diff --git a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java index d14d36523658..cde7955f619b 100644 --- a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java +++ b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java @@ -397,7 +397,7 @@ public class ProjectTypeStep extends ModuleWizardStep implements Disposable { private boolean showCustomOptions(@NotNull ModuleBuilder builder) { String card = builder.getBuilderId(); if (!myCustomSteps.containsKey(card)) { - ModuleWizardStep step = builder.getCustomOptionsStep(this); + ModuleWizardStep step = builder.getCustomOptionsStep(myContext, this); if (step == null) return false; step.updateStep(); myCustomSteps.put(card, step); diff --git a/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/impl/FrameworkSupportModelBase.java b/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/impl/FrameworkSupportModelBase.java index 5046be673abb..b975ae43371d 100644 --- a/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/impl/FrameworkSupportModelBase.java +++ b/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/impl/FrameworkSupportModelBase.java @@ -180,17 +180,12 @@ public abstract class FrameworkSupportModelBase extends UserDataHolderBase imple } public void onFrameworkSelectionChanged(FrameworkSupportNode node) { - final FrameworkSupportModelListener multicaster = myDispatcher.getMulticaster(); final FrameworkSupportInModuleProvider provider = node.getUserObject(); //todo[nik] + boolean checked = node.isChecked(); if (provider instanceof OldFrameworkSupportProviderWrapper) { final FrameworkSupportProvider oldProvider = ((OldFrameworkSupportProviderWrapper) provider).getProvider(); - if (node.isChecked()) { - multicaster.frameworkSelected(oldProvider); - } - else { - multicaster.frameworkUnselected(oldProvider); - } + selectFramework(oldProvider, checked); } for (FrameworkSupportInModuleProvider.FrameworkDependency dependency : provider.getDependenciesFrameworkIds()) { if (!dependency.isOptional()) { @@ -200,6 +195,16 @@ public abstract class FrameworkSupportModelBase extends UserDataHolderBase imple } } + public void selectFramework(FrameworkSupportProvider provider, boolean checked) { + final FrameworkSupportModelListener multicaster = myDispatcher.getMulticaster(); + if (checked) { + multicaster.frameworkSelected(provider); + } + else { + multicaster.frameworkUnselected(provider); + } + } + public void fireWizardStepUpdated() { myDispatcher.getMulticaster().wizardStepUpdated(); } diff --git a/java/idea-ui/src/com/intellij/ide/util/projectWizard/SdkSettingsStep.java b/java/idea-ui/src/com/intellij/ide/util/projectWizard/SdkSettingsStep.java index 4db93153f581..4e42026647e2 100644 --- a/java/idea-ui/src/com/intellij/ide/util/projectWizard/SdkSettingsStep.java +++ b/java/idea-ui/src/com/intellij/ide/util/projectWizard/SdkSettingsStep.java @@ -17,12 +17,8 @@ package com.intellij.ide.util.projectWizard; import com.intellij.CommonBundle; import com.intellij.ide.IdeBundle; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.projectRoots.ProjectJdkTable; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SdkTypeId; import com.intellij.openapi.roots.ProjectRootManager; @@ -35,8 +31,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; /** * @author Dmitry Avdeev @@ -47,64 +41,29 @@ public class SdkSettingsStep extends ModuleWizardStep { protected final WizardContext myWizardContext; protected final ProjectSdksModel myModel; private final ModuleBuilder myModuleBuilder; + private final JPanel myJdkPanel; - public SdkSettingsStep(@NotNull SettingsStep settingsStep, @NotNull ModuleBuilder moduleBuilder, @NotNull Condition sdkFilter) { + public SdkSettingsStep(SettingsStep settingsStep, @NotNull ModuleBuilder moduleBuilder, + @NotNull Condition sdkFilter) { + + this(settingsStep.getContext(), moduleBuilder, sdkFilter); + settingsStep.addSettingsField(getSdkFieldLabel(settingsStep.getContext().getProject()), myJdkPanel); + } + + public SdkSettingsStep(WizardContext context, + @NotNull ModuleBuilder moduleBuilder, + @NotNull Condition sdkFilter) { myModuleBuilder = moduleBuilder; - myWizardContext = settingsStep.getContext(); + myWizardContext = context; myModel = new ProjectSdksModel(); Project project = myWizardContext.getProject(); myModel.reset(project); - myJdkComboBox = new JdkComboBox(myModel, sdkFilter); - - final PropertiesComponent component = project == null ? PropertiesComponent.getInstance() : PropertiesComponent.getInstance(project); - ModuleType moduleType = moduleBuilder.getModuleType(); - final String selectedJdkProperty = "jdk.selected." + (moduleType == null ? "" : moduleType.getId()); - myJdkComboBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Sdk jdk = myJdkComboBox.getSelectedJdk(); - if (jdk != null) { - component.setValue(selectedJdkProperty, jdk.getName()); - } - } - }); - - if (project != null) { - Sdk sdk = ProjectRootManager.getInstance(project).getProjectSdk(); - if (sdk != null && moduleBuilder.isSuitableSdkType(sdk.getSdkType())) { - // use project SDK - return; - } - } - else { - // set default project SDK - Project defaultProject = ProjectManager.getInstance().getDefaultProject(); - Sdk sdk = ProjectRootManager.getInstance(defaultProject).getProjectSdk(); - if (sdk != null && sdkFilter.value(sdk.getSdkType())) { - myJdkComboBox.setSelectedJdk(sdk); - } - } - - String value = component.getValue(selectedJdkProperty); - if (value != null) { - Sdk jdk = ProjectJdkTable.getInstance().findJdk(value); - if (jdk != null) { - myJdkComboBox.setSelectedJdk(jdk); - } - } - - JButton button = new JButton("Ne\u001Bw..."); - myJdkComboBox.setSetupButton(button, project, myModel, - project == null ? new JdkComboBox.NoneJdkComboBoxItem() : new JdkComboBox.ProjectJdkComboBoxItem(), - null, - false); - JPanel jdkPanel = new JPanel(new BorderLayout(4, 0)); - jdkPanel.add(myJdkComboBox); - jdkPanel.add(button, BorderLayout.EAST); - settingsStep.addSettingsField(getSdkFieldLabel(project), jdkPanel); - + myJdkComboBox = JdkComboBox.createSdkComboBox(moduleBuilder, sdkFilter, project, myModel); + myJdkPanel = new JPanel(new BorderLayout(4, 0)); + myJdkPanel.add(myJdkComboBox); + myJdkPanel.add(myJdkComboBox.getSetUpButton(), BorderLayout.EAST); } @NotNull @@ -112,9 +71,13 @@ public class SdkSettingsStep extends ModuleWizardStep { return (project == null ? "Project" : "Module") + " \u001BSDK:"; } + public JdkComboBox getJdkComboBox() { + return myJdkComboBox; + } + @Override public JComponent getComponent() { - return null; + return myJdkPanel; } @Override diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JdkComboBox.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JdkComboBox.java index f81f84064583..8a8a55461cb9 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JdkComboBox.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JdkComboBox.java @@ -16,13 +16,19 @@ package com.intellij.openapi.roots.ui.configuration; import com.intellij.ide.DataManager; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.ide.util.projectWizard.ModuleBuilder; import com.intellij.ide.util.projectWizard.ProjectJdkListRenderer; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.projectRoots.ProjectJdkTable; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SdkType; import com.intellij.openapi.projectRoots.SdkTypeId; +import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.roots.ui.configuration.projectRoot.JdkListConfigurable; import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; import com.intellij.openapi.ui.ComboBoxWithWidePopup; @@ -57,6 +63,7 @@ public class JdkComboBox extends ComboBoxWithWidePopup { private final Condition myFilter; @Nullable private final Condition myCreationFilter; + private JButton mySetUpButton; public JdkComboBox(@NotNull final ProjectSdksModel jdkModel) { this(jdkModel, null); @@ -103,6 +110,57 @@ public class JdkComboBox extends ComboBoxWithWidePopup { }); } + public static JdkComboBox createSdkComboBox(ModuleBuilder moduleBuilder, + Condition sdkFilter, + Project project, + ProjectSdksModel model) { + final JdkComboBox comboBox = new JdkComboBox(model, sdkFilter); + + final PropertiesComponent component = project == null ? PropertiesComponent.getInstance() : PropertiesComponent.getInstance(project); + ModuleType moduleType = moduleBuilder.getModuleType(); + final String selectedJdkProperty = "jdk.selected." + (moduleType == null ? "" : moduleType.getId()); + comboBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Sdk jdk = comboBox.getSelectedJdk(); + if (jdk != null) { + component.setValue(selectedJdkProperty, jdk.getName()); + } + } + }); + + if (project != null) { + Sdk sdk = ProjectRootManager.getInstance(project).getProjectSdk(); + if (sdk != null && moduleBuilder.isSuitableSdkType(sdk.getSdkType())) { + // use project SDK + return null; + } + } + else { + // set default project SDK + Project defaultProject = ProjectManager.getInstance().getDefaultProject(); + Sdk sdk = ProjectRootManager.getInstance(defaultProject).getProjectSdk(); + if (sdk != null && sdkFilter.value(sdk.getSdkType())) { + comboBox.setSelectedJdk(sdk); + } + } + + String value = component.getValue(selectedJdkProperty); + if (value != null) { + Sdk jdk = ProjectJdkTable.getInstance().findJdk(value); + if (jdk != null) { + comboBox.setSelectedJdk(jdk); + } + } + + JButton button = new JButton("Ne\u001Bw..."); + comboBox.setSetupButton(button, project, model, + project == null ? new NoneJdkComboBoxItem() : new ProjectJdkComboBoxItem(), + null, + false); + return comboBox; + } + @Override public Dimension getPreferredSize() { final Rectangle rec = ScreenUtil.getScreenRectangle(0, 0); @@ -140,7 +198,9 @@ public class JdkComboBox extends ComboBoxWithWidePopup { final JdkComboBoxItem firstItem, @Nullable final Condition additionalSetup, final String actionGroupTitle) { - setUpButton.addActionListener(new ActionListener() { + + mySetUpButton = setUpButton; + mySetUpButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { DefaultActionGroup group = new DefaultActionGroup(); @@ -199,6 +259,10 @@ public class JdkComboBox extends ComboBoxWithWidePopup { }); } + public JButton getSetUpButton() { + return mySetUpButton; + } + @Override public JdkComboBoxItem getSelectedItem() { return (JdkComboBoxItem)super.getSelectedItem(); diff --git a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowFactory.java b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowFactory.java index 0a25ea3aae42..2ba1906c422c 100644 --- a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowFactory.java +++ b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowFactory.java @@ -161,7 +161,7 @@ public class ControlFlowFactory { Reference> cachedRef = cachedFlows.get(element); ConcurrentList cached = com.intellij.reference.SoftReference.dereference(cachedRef); if (cached == null) { - cached = ContainerUtil.createLockFreeCopyOnWriteList(); + cached = ContainerUtil.createConcurrentList(); cachedFlows.put(element, new SoftReference>(cached)); } return cached; diff --git a/java/java-tests/testSrc/com/intellij/concurrency/JobUtilTest.java b/java/java-tests/testSrc/com/intellij/concurrency/JobUtilTest.java index fd68abd89066..d0ab59b598d1 100644 --- a/java/java-tests/testSrc/com/intellij/concurrency/JobUtilTest.java +++ b/java/java-tests/testSrc/com/intellij/concurrency/JobUtilTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -30,6 +30,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -73,7 +75,7 @@ public class JobUtilTest extends PlatformTestCase { return COUNT.incrementAndGet(); } - public void testJobUtilFinishes() throws Exception { + public void testJobUtilCorrectlySplitsUpHugeWorkAndFinishes() throws Exception { COUNT.set(0); int N = 100000; List list = Collections.nCopies(N, null); @@ -259,7 +261,7 @@ public class JobUtilTest extends PlatformTestCase { public void testJobUtilRecursiveCancel() throws Exception { final List list = Collections.nCopies(100, ""); final List ilist = Collections.nCopies(100, 0); - for (int i=0; i<1/*0*/; i++) { + for (int i=0; i<10; i++) { COUNT.set(0); long start = System.currentTimeMillis(); boolean success = false; @@ -277,7 +279,7 @@ public class JobUtilTest extends PlatformTestCase { return true; } }); - System.out.println("nestedSuccess = " + nestedSuccess); + //System.out.println("nestedSuccess = " + nestedSuccess); return true; } }); @@ -294,4 +296,35 @@ public class JobUtilTest extends PlatformTestCase { assertFalse(success); } } + + public void testSaturation() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + for (int i=0; i<100; i++) { + JobLauncher.getInstance().submitToJobThread(0, new Runnable() { + @Override + public void run() { + try { + latch.await(); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); + } + JobLauncher.getInstance().submitToJobThread(0, new Runnable() { + @Override + public void run() { + latch.countDown(); + } + }); + + try { + boolean scheduled = latch.await(3, TimeUnit.SECONDS); + assertFalse(scheduled); // pool saturated, no thread can be scheduled + } + finally { + latch.countDown(); + } + } } diff --git a/java/openapi/src/com/intellij/ide/util/projectWizard/JavaModuleBuilder.java b/java/openapi/src/com/intellij/ide/util/projectWizard/JavaModuleBuilder.java index 3184488e0a33..655dc956c399 100644 --- a/java/openapi/src/com/intellij/ide/util/projectWizard/JavaModuleBuilder.java +++ b/java/openapi/src/com/intellij/ide/util/projectWizard/JavaModuleBuilder.java @@ -27,7 +27,6 @@ import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.LibraryTable; -import com.intellij.openapi.roots.ui.configuration.ModulesProvider; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; @@ -90,11 +89,6 @@ public class JavaModuleBuilder extends ModuleBuilder implements SourcePathsBuild return StdModuleTypes.JAVA.modifySettingsStep(settingsStep, this); } - @Override - public ModuleWizardStep[] createWizardSteps(@NotNull WizardContext wizardContext, @NotNull ModulesProvider modulesProvider) { - return getModuleType().createWizardSteps(wizardContext, this, modulesProvider); - } - public void setupRootModel(ModifiableRootModel rootModel) throws ConfigurationException { final CompilerModuleExtension compilerModuleExtension = rootModel.getModuleExtension(CompilerModuleExtension.class); compilerModuleExtension.setExcludeOutput(true); diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/inspection-black-list.txt b/platform/analysis-impl/src/com/intellij/codeInspection/inspection-black-list.txt index c78817d1f52c..18ea3ddef8d5 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/inspection-black-list.txt +++ b/platform/analysis-impl/src/com/intellij/codeInspection/inspection-black-list.txt @@ -76,7 +76,7 @@ com.siyeh.ig.bugs.ReturnNullInspection com.siyeh.ig.classlayout.EmptyClassInspection com.siyeh.ig.classlayout.InnerClassOnInterfaceInspection com.siyeh.ig.classlayout.ListenerMayUseAdapterInspection -com.siyeh.ig.classlayout.MissingOverrideAnnotationInspection +com.siyeh.ig.inheritance.MissingOverrideAnnotationInspection com.siyeh.ig.classlayout.UtilityClassInspection com.siyeh.ig.classlayout.UtilityClassWithoutPrivateConstructorInspection com.siyeh.ig.classmetrics.AnonymousClassComplexityInspection @@ -163,7 +163,6 @@ com.siyeh.ig.methodmetrics.ThreeNegationsPerMethodInspection com.siyeh.ig.methodmetrics.ThrownExceptionsPerMethodInspection com.siyeh.ig.migration.ForCanBeForeachInspection com.siyeh.ig.migration.IfCanBeSwitchInspection -com.siyeh.ig.migration.MethodCanBeVariableArityMethodInspection com.siyeh.ig.modularization.ModuleWithTooFewClassesInspection com.siyeh.ig.modularization.ModuleWithTooManyClassesInspection com.siyeh.ig.naming.AnnotationNamingConventionInspection diff --git a/platform/core-impl/src/com/intellij/concurrency/JobLauncher.java b/platform/core-impl/src/com/intellij/concurrency/JobLauncher.java index c5f6671ac126..47cea3cf3682 100644 --- a/platform/core-impl/src/com/intellij/concurrency/JobLauncher.java +++ b/platform/core-impl/src/com/intellij/concurrency/JobLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -67,9 +67,19 @@ public abstract class JobLauncher { @NotNull Processor thingProcessor); + /** + * NEVER EVER submit runnable which can lock itself for indeterminate amount of time. + * This will cause deadlock since this thread pool is an easily exhaustible resource. + * Use {@link com.intellij.openapi.application.Application#executeOnPooledThread(java.lang.Runnable)} instead + */ @NotNull public abstract Job submitToJobThread(int priority, @NotNull final Runnable action, @Nullable Consumer onDoneCallback); + /** + * NEVER EVER submit runnable which can lock itself for indeterminate amount of time. + * This will cause deadlock since this thread pool is an easily exhaustible resource. + * Use {@link com.intellij.openapi.application.Application#executeOnPooledThread(java.lang.Runnable)} instead + */ @NotNull public Job submitToJobThread(int priority, @NotNull final Runnable action) { return submitToJobThread(priority, action, null); diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java index 42cf2192351e..db813c8a3132 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java @@ -64,7 +64,7 @@ public class PluginManagerCore { public static final float PLUGINS_PROGRESS_MAX_VALUE = 0.3f; static final Map ourId2Index = new THashMap(); @NonNls static final String MODULE_DEPENDENCY_PREFIX = "com.intellij.module"; - static final List ourAvailableModules = new ArrayList(); + static final Map ourModulesToContainingPlugins = new HashMap(); static final PluginClassCache ourPluginClasses = new PluginClassCache(); @NonNls static final String SPECIAL_IDEA_PLUGIN = "IDEA CORE"; static final String DISABLE = "disable"; @@ -236,7 +236,8 @@ public class PluginManagerCore { if (processed.contains(dependentPluginId)) continue; // TODO[yole] should this condition be a parameter? - if (isModuleDependency(dependentPluginId) && (ourAvailableModules.isEmpty() || ourAvailableModules.contains(dependentPluginId.getIdString()))) { + if (isModuleDependency(dependentPluginId) && (ourModulesToContainingPlugins.isEmpty() || ourModulesToContainingPlugins.containsKey( + dependentPluginId.getIdString()))) { continue; } if (!optionalDependencies.contains(dependentPluginId)) { @@ -483,9 +484,8 @@ public class PluginManagerCore { } private static void addModulesAsDependents(Map map) { - for (String module : ourAvailableModules) { - // fake plugin descriptors to satisfy dependencies - map.put(PluginId.getId(module), new IdeaPluginDescriptorImpl()); + for (Map.Entry entry : ourModulesToContainingPlugins.entrySet()) { + map.put(PluginId.getId(entry.getKey()), entry.getValue()); } } @@ -967,7 +967,7 @@ public class PluginManagerCore { } final List pluginIds = pluginId == null ? null : StringUtil.split(pluginId, ","); - final boolean checkModuleDependencies = !ourAvailableModules.isEmpty() && !ourAvailableModules.contains("com.intellij.modules.all"); + final boolean checkModuleDependencies = !ourModulesToContainingPlugins.isEmpty() && !ourModulesToContainingPlugins.containsKey("com.intellij.modules.all"); if (checkModuleDependencies && !hasModuleDependencies(descriptor)) { return true; } @@ -1058,7 +1058,11 @@ public class PluginManagerCore { for (IdeaPluginDescriptorImpl descriptor : pluginDescriptors) { final List modules = descriptor.getModules(); if (modules != null) { - ourAvailableModules.addAll(modules); + for (String module : modules) { + if (!ourModulesToContainingPlugins.containsKey(module)) { + ourModulesToContainingPlugins.put(module, descriptor); + } + } } if (!shouldSkipPlugin(descriptor, pluginDescriptors)) { @@ -1087,6 +1091,7 @@ public class PluginManagerCore { } mergeOptionalConfigs(idToDescriptorMap); + addModulesAsDependents(idToDescriptorMap); // sort descriptors according to plugin dependencies Collections.sort(result, getPluginDescriptorComparator(idToDescriptorMap)); diff --git a/platform/lang-api/src/com/intellij/ide/util/projectWizard/AbstractModuleBuilder.java b/platform/lang-api/src/com/intellij/ide/util/projectWizard/AbstractModuleBuilder.java index b25326d45bf4..aa9bdaa21c14 100644 --- a/platform/lang-api/src/com/intellij/ide/util/projectWizard/AbstractModuleBuilder.java +++ b/platform/lang-api/src/com/intellij/ide/util/projectWizard/AbstractModuleBuilder.java @@ -43,7 +43,7 @@ public abstract class AbstractModuleBuilder extends ProjectBuilder { * Custom UI to be shown on the first wizard page */ @Nullable - public ModuleWizardStep getCustomOptionsStep(Disposable parentDisposable) { + public ModuleWizardStep getCustomOptionsStep(WizardContext context, Disposable parentDisposable) { return null; } diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyPanel.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/DirectoryFormattingOptions.java similarity index 76% rename from platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyPanel.java rename to platform/lang-impl/src/com/intellij/codeInsight/actions/DirectoryFormattingOptions.java index e03cfa394b5f..9a4c08e94361 100644 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyPanel.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/DirectoryFormattingOptions.java @@ -13,16 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.remoteServer.util.ssh; +package com.intellij.codeInsight.actions; -import javax.swing.*; +public interface DirectoryFormattingOptions extends ReformatFilesOptions { -/** - * @author michael.golubev - */ -public interface PublicSshKeyPanel { + boolean isIncludeSubdirectories(); - String getSshKey(); - - JComponent getMainPanel(); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeOptions.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeOptions.java index f4691c3e1f31..5a05a9ac13ab 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeOptions.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeOptions.java @@ -15,12 +15,10 @@ */ package com.intellij.codeInsight.actions; -public interface LayoutCodeOptions extends ReformatFilesOptions { +public interface LayoutCodeOptions extends DirectoryFormattingOptions { boolean isProcessWholeFile(); boolean isProcessDirectory(); - boolean isIncludeSubdirectories(); - } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutDirectoryDialog.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutDirectoryDialog.java new file mode 100644 index 000000000000..e8be41b8eb05 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutDirectoryDialog.java @@ -0,0 +1,62 @@ +/* + * 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.intellij.codeInsight.actions; + +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; + +public class LayoutDirectoryDialog extends LayoutProjectCodeDialog implements DirectoryFormattingOptions { + private JCheckBox myIncludeSubdirs; + + public LayoutDirectoryDialog(@NotNull Project project, + String title, + String text, + boolean enableOnlyVCSChangedTextCb) + { + super(project, title, text, true, enableOnlyVCSChangedTextCb); + } + + public void setEnabledIncludeSubdirsCb(boolean isEnabled) { + myIncludeSubdirs.setEnabled(isEnabled); + } + + public void setSelectedIncludeSubdirsCb(boolean isSelected) { + myIncludeSubdirs.setSelected(isSelected); + } + + @Override + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new BorderLayout(0, 7)); + JComponent layoutProjectDialog = super.createCenterPanel(); + + myIncludeSubdirs = new JCheckBox(CodeInsightBundle.message("reformat.option.include.subdirectories")); + + panel.add(layoutProjectDialog, BorderLayout.CENTER); + panel.add(myIncludeSubdirs, BorderLayout.SOUTH); + + return panel; + } + + @Override + public boolean isIncludeSubdirectories() { + return myIncludeSubdirs.isSelected(); + } + +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java index dd54474fbfd0..aa4aadc9a083 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java @@ -19,12 +19,10 @@ package com.intellij.codeInsight.actions; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.help.HelpManager; -import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -36,26 +34,27 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi private static @NonNls final String HELP_ID = "editing.codeReformatting"; private final String myText; + private final boolean myEnableOnlyVCSChangedTextCb; private final boolean mySuggestOptimizeImports; private final Project myProject; - private final Module myModule; + private JCheckBox myCbOptimizeImports; private JCheckBox myCbOnlyVcsChangedRegions; private JCheckBox myCbRearrangeEntries; public LayoutProjectCodeDialog(@NotNull Project project, - @Nullable Module module, String title, String text, - boolean suggestOptimizeImports) + boolean suggestOptimizeImports, + boolean enableOnlyVCSChangedTextCb) { super(project, false); myText = text; mySuggestOptimizeImports = suggestOptimizeImports; myProject = project; - myModule = module; - + myEnableOnlyVCSChangedTextCb = enableOnlyVCSChangedTextCb; + setOKButtonText(CodeInsightBundle.message("reformat.code.accept.button.text")); setTitle(title); init(); @@ -63,26 +62,26 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi @Override protected JComponent createCenterPanel() { - if (!mySuggestOptimizeImports) return new JLabel(myText); + if (!mySuggestOptimizeImports) { + return new JLabel(myText); + } + JPanel panel = new JPanel(new GridLayout(4, 1)); - panel.add(new JLabel(myText)); myCbOptimizeImports = new JCheckBox(CodeInsightBundle.message("reformat.option.optimize.imports")); - panel.add(myCbOptimizeImports); - myCbOptimizeImports.setSelected(PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, false)); - - myCbOnlyVcsChangedRegions = new JCheckBox(CodeInsightBundle.message("reformat.option.vcs.changed.region")); - panel.add(myCbOnlyVcsChangedRegions); - final boolean canTargetVcsRegions = canTargetVcsRegions(); - myCbOnlyVcsChangedRegions.setEnabled(canTargetVcsRegions); - myCbOnlyVcsChangedRegions.setSelected( - canTargetVcsRegions && PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, false) - ); - - myCbRearrangeEntries = new JCheckBox(CodeInsightBundle.message("reformat.option.rearrange.entries")); + myCbOnlyVcsChangedRegions = new JCheckBox(CodeInsightBundle.message("reformat.option.vcs.changed.region")); + + panel.add(new JLabel(myText)); + panel.add(myCbOptimizeImports); panel.add(myCbRearrangeEntries); - boolean previousSelectedState = LayoutCodeSettingsStorage.getLastSavedRearrangeEntriesCbStateFor(myProject); - myCbRearrangeEntries.setSelected(previousSelectedState); + panel.add(myCbOnlyVcsChangedRegions); + + myCbOptimizeImports.setSelected(PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, false)); + myCbRearrangeEntries.setSelected(LayoutCodeSettingsStorage.getLastSavedRearrangeEntriesCbStateFor(myProject)); + myCbOnlyVcsChangedRegions.setEnabled(myEnableOnlyVCSChangedTextCb); + myCbOnlyVcsChangedRegions.setSelected( + myEnableOnlyVCSChangedTextCb && PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, false) + ); return panel; } @@ -109,6 +108,9 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi if (mySuggestOptimizeImports) { PropertiesComponent.getInstance().setValue(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, Boolean.toString(isOptimizeImports())); LayoutCodeSettingsStorage.saveRearrangeEntriesOptionFor(myProject, isRearrangeEntries()); + if (myEnableOnlyVCSChangedTextCb) { + PropertiesComponent.getInstance().setValue(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, Boolean.toString(myCbOnlyVcsChangedRegions.isSelected())); + } } } @@ -119,12 +121,5 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi public boolean isProcessOnlyChangedText() { return myCbOnlyVcsChangedRegions.isEnabled() && myCbOnlyVcsChangedRegions.isSelected(); } - - private boolean canTargetVcsRegions() { - if (myModule != null) { - return FormatChangedTextUtil.hasChanges(myModule); - } - return FormatChangedTextUtil.hasChanges(myProject); - } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java index 97555a347fbd..bdcb7779d981 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java @@ -75,7 +75,7 @@ public class OptimizeImportsAction extends AnAction { text = CodeInsightBundle.message("process.scope.project", projectContext.getPresentableUrl()); } LayoutProjectCodeDialog dialog - = new LayoutProjectCodeDialog(project, null, CodeInsightBundle.message("process.optimize.imports"), text, false); + = new LayoutProjectCodeDialog(project, CodeInsightBundle.message("process.optimize.imports"), text, false, FormatChangedTextUtil.hasChanges(project)); dialog.show(); if (!dialog.isOK()) return; if (moduleContext != null) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java index 215e9a56ec40..42bfdb390ed3 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java @@ -139,7 +139,14 @@ public class ReformatCodeAction extends AnAction implements DumbAware { final boolean showDialog = EditorSettingsExternalizable.getInstance().getOptions().SHOW_REFORMAT_DIALOG; - if (showDialog || (file == null && dir != null)) { + if (file == null && dir != null) { + DirectoryFormattingOptions options = getDirectoryFormattingOptions(project, dir); + if (options != null) { + reformatDirectory(project, dir, options); + } + return; + } + else if (showDialog) { LayoutCodeOptions selectedFlags = getLayoutCodeOptions(project, file, dir, hasSelection); if (selectedFlags == null) return; @@ -148,21 +155,14 @@ public class ReformatCodeAction extends AnAction implements DumbAware { rearrangeEntries = selectedFlags.isRearrangeEntries(); processWholeFile = selectedFlags.isProcessWholeFile(); processChangedTextOnly = selectedFlags.isProcessOnlyChangedText(); - - if (selectedFlags.isProcessDirectory()) { - AbstractLayoutCodeProcessor processor = new ReformatCodeProcessor(project, dir, selectedFlags.isIncludeSubdirectories(), processChangedTextOnly); - if (optimizeImports) { - processor = new OptimizeImportsProcessor(processor); - } - if (selectedFlags.isRearrangeEntries()) { - processor = new RearrangeCodeProcessor(processor, null); - } - processor.run(); + if (selectedFlags.isProcessDirectory()) { + assert dir != null : "File = " + file + ", Element = " + CommonDataKeys.PSI_ELEMENT.getData(dataContext); + reformatDirectory(project, dir, selectedFlags); return; } } - + final TextRange range; if (!processWholeFile && editor != null && editor.getSelectionModel().hasSelection()){ range = TextRange.create(editor.getSelectionModel().getSelectionStart(), editor.getSelectionModel().getSelectionEnd()); @@ -172,12 +172,7 @@ public class ReformatCodeAction extends AnAction implements DumbAware { } if (optimizeImports && range == null) { - if (file != null || dir == null) { - new OptimizeImportsProcessor(new ReformatCodeProcessor(project, file, null, processChangedTextOnly)).run(); - } - else { - new OptimizeImportsProcessor(new ReformatCodeProcessor(project, dir, true, processChangedTextOnly)).run(); - } + new OptimizeImportsProcessor(new ReformatCodeProcessor(project, file, null, processChangedTextOnly)).run(); } else { new ReformatCodeProcessor(project, file, range, processChangedTextOnly).run(); @@ -204,6 +199,47 @@ public class ReformatCodeAction extends AnAction implements DumbAware { } } + @Nullable + private static DirectoryFormattingOptions getDirectoryFormattingOptions(@NotNull Project project, @NotNull PsiDirectory dir) { + LayoutDirectoryDialog dialog = new LayoutDirectoryDialog( + project, + CodeInsightBundle.message("process.reformat.code"), + CodeInsightBundle.message("process.scope.directory", dir.getVirtualFile().getPath()), + FormatChangedTextUtil.hasChanges(dir) + ); + + boolean enableIncludeDirectoriesCb = dir.getSubdirectories().length > 0; + dialog.setEnabledIncludeSubdirsCb(enableIncludeDirectoriesCb); + dialog.setSelectedIncludeSubdirsCb(enableIncludeDirectoriesCb); + + dialog.show(); + if (dialog.isOK()) { + return dialog; + } + return null; + } + + private static void reformatDirectory(@NotNull Project project, + @NotNull PsiDirectory dir, + @NotNull DirectoryFormattingOptions options) + { + AbstractLayoutCodeProcessor processor = new ReformatCodeProcessor( + project, + dir, + options.isIncludeSubdirectories(), + options.isProcessOnlyChangedText() + ); + + if (options.isOptimizeImports()) { + processor = new OptimizeImportsProcessor(processor); + } + if (options.isRearrangeEntries()) { + processor = new RearrangeCodeProcessor(processor, null); + } + + processor.run(); + } + private static void reformatModule(@NotNull Project project, @Nullable Module moduleContext, @NotNull ReformatFilesOptions selectedFlags) @@ -345,7 +381,10 @@ public class ReformatCodeAction extends AnAction implements DumbAware { final String text = module != null ? CodeInsightBundle.message("process.scope.module", module.getModuleFilePath()) : CodeInsightBundle.message("process.scope.project", project.getPresentableUrl()); - LayoutProjectCodeDialog dialog = new LayoutProjectCodeDialog(project, module, CodeInsightBundle.message("process.reformat.code"), text, true); + final boolean enableOnlyVCSChangedRegions = module != null ? FormatChangedTextUtil.hasChanges(module) + : FormatChangedTextUtil.hasChanges(project); + + LayoutProjectCodeDialog dialog = new LayoutProjectCodeDialog(project, CodeInsightBundle.message("process.reformat.code"), text, true, enableOnlyVCSChangedRegions); dialog.show(); if (!dialog.isOK()) return null; return dialog; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java index d825a624b6c3..2ced9a6b55b2 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java @@ -138,7 +138,10 @@ public class TypedHandler extends TypedActionHandlerBase { Project project = CommonDataKeys.PROJECT.getData(dataContext); PsiFile file; - if (project == null || editor.isColumnMode() || (file = PsiUtilBase.getPsiFileInEditor(editor, project)) == null) { + if (project == null + || editor.isColumnMode() + || editor.getCaretModel().getAllCarets().size() > 1 + || (file = PsiUtilBase.getPsiFileInEditor(editor, project)) == null) { if (myOriginalHandler != null){ myOriginalHandler.execute(editor, charTyped, dataContext); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/highlighting/BraceHighlightingHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/highlighting/BraceHighlightingHandler.java index 00346e1e6cd7..92d74cfef60f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/highlighting/BraceHighlightingHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/highlighting/BraceHighlightingHandler.java @@ -26,8 +26,6 @@ package com.intellij.codeInsight.highlighting; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.hint.EditorFragmentComponent; -import com.intellij.concurrency.Job; -import com.intellij.concurrency.JobLauncher; import com.intellij.injected.editor.EditorWindow; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; @@ -115,7 +113,7 @@ public class BraceHighlightingHandler { } final int offset = editor.getCaretModel().getOffset(); final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project); - JobLauncher.getInstance().submitToJobThread(Job.DEFAULT_PRIORITY, new Runnable() { + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override public void run() { if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(new Runnable() { @@ -123,8 +121,11 @@ public class BraceHighlightingHandler { public void run() { final PsiFile injected; try { - injected = psiFile == null || psiFile instanceof PsiCompiledElement || psiFile instanceof PsiBinaryFile || isReallyDisposed(editor, project) - ? null : getInjectedFileIfAny(editor, project, offset, psiFile, alarm); + injected = psiFile == null || + psiFile instanceof PsiCompiledElement || + psiFile instanceof PsiBinaryFile || + isReallyDisposed(editor, project) + ? null : getInjectedFileIfAny(editor, project, offset, psiFile, alarm); } catch (RuntimeException e) { // Reset processing flag in case of unexpected exception. diff --git a/platform/lang-impl/src/com/intellij/execution/console/BaseConsoleExecuteActionHandler.java b/platform/lang-impl/src/com/intellij/execution/console/BaseConsoleExecuteActionHandler.java index 64138ffa597e..6df77d40681a 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/BaseConsoleExecuteActionHandler.java +++ b/platform/lang-impl/src/com/intellij/execution/console/BaseConsoleExecuteActionHandler.java @@ -60,8 +60,7 @@ public abstract class BaseConsoleExecuteActionHandler { console.setInputText(""); - UndoManager manager = UndoManager.getInstance(console.getProject()); - ((UndoManagerImpl)manager).invalidateActionsFor(DocumentReferenceManager.getInstance().create(document)); + ((UndoManagerImpl)UndoManager.getInstance(console.getProject())).invalidateActionsFor(DocumentReferenceManager.getInstance().create(document)); myConsoleHistoryModel.addToHistory(text); if (consoleView == null) { diff --git a/platform/lang-impl/src/com/intellij/execution/console/ConsoleGutterComponent.java b/platform/lang-impl/src/com/intellij/execution/console/ConsoleGutterComponent.java index 1c9108234e03..c6cb0737c857 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/ConsoleGutterComponent.java +++ b/platform/lang-impl/src/com/intellij/execution/console/ConsoleGutterComponent.java @@ -3,22 +3,13 @@ package com.intellij.execution.console; import com.intellij.codeInsight.hint.TooltipController; import com.intellij.codeInsight.hint.TooltipGroup; import com.intellij.ide.ui.UISettings; -import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.impl.ApplicationImpl; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.VisualPosition; import com.intellij.openapi.editor.colors.EditorFontType; -import com.intellij.openapi.editor.event.DocumentAdapter; -import com.intellij.openapi.editor.event.DocumentEvent; -import com.intellij.openapi.editor.ex.DocumentBulkUpdateListener; -import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.EditorMarkupModel; import com.intellij.openapi.editor.impl.EditorImpl; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.HintHint; import com.intellij.ui.JBColor; import com.intellij.ui.awt.RelativePoint; @@ -32,10 +23,11 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; -public class ConsoleGutterComponent extends JComponent implements MouseMotionListener { +class ConsoleGutterComponent extends JComponent implements MouseMotionListener { private static final TooltipGroup TOOLTIP_GROUP = new TooltipGroup("CONSOLE_GUTTER_TOOLTIP_GROUP", 0); + private static final int RIGHT_INSET = 6; - private int annotationGuttersSize = 0; + private int maxAnnotationWidth = 0; private int myLastPreferredHeight = -1; private final EditorImpl editor; @@ -43,65 +35,29 @@ public class ConsoleGutterComponent extends JComponent implements MouseMotionLis private int lastGutterToolTipLine = -1; - public ConsoleGutterComponent(@NotNull Editor editor, @NotNull GutterContentProvider provider, @NotNull Disposable parentDisposable) { + public ConsoleGutterComponent(@NotNull Editor editor, @NotNull GutterContentProvider provider) { this.editor = (EditorImpl)editor; gutterContentProvider = provider; - addListeners(parentDisposable); + addListeners(); addMouseMotionListener(this); } - private void addListeners(@NotNull Disposable parentDisposable) { - Project project = editor.getProject(); - assert project != null; - project.getMessageBus().connect(parentDisposable).subscribe(DocumentBulkUpdateListener.TOPIC, new DocumentBulkUpdateListener.Adapter() { - @Override - public void updateFinished(@NotNull Document document) { - if (document.getTextLength() == 0) { - gutterContentProvider.documentCleared(editor); - } - updateSize(); - } - }); - - editor.getDocument().addDocumentListener(new DocumentAdapter() { - @Override - public void documentChanged(DocumentEvent event) { - DocumentEx document = editor.getDocument(); - if (document.isInBulkUpdate()) { - return; - } - - if (document.getTextLength() > 0) { - int startDocLine = document.getLineNumber(event.getOffset()); - int endDocLine = document.getLineNumber(event.getOffset() + event.getNewLength()); - if (event.getOldLength() > event.getNewLength() || startDocLine != endDocLine || StringUtil.indexOf(event.getOldFragment(), '\n') != -1) { - updateSize(); - } - } - else if (event.getOldLength() > 0) { - gutterContentProvider.documentCleared(editor); - } - } - }); - + private void addListeners() { addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - if (e.isPopupTrigger()) { - return; + if (!e.isPopupTrigger()) { + gutterContentProvider.doAction(getLineAtPoint(e.getPoint()), editor); } - - int line = getLineAtPoint(e.getPoint()); - gutterContentProvider.doAction(line, editor); } }); } public void updateSize() { - int oldAnnotationsWidth = annotationGuttersSize; - computeAnnotationsSize(); - if (oldAnnotationsWidth != annotationGuttersSize || myLastPreferredHeight != editor.getPreferredHeight()) { + int oldAnnotationsWidth = maxAnnotationWidth; + computeMaxAnnotationWidth(); + if (oldAnnotationsWidth != maxAnnotationWidth || myLastPreferredHeight != editor.getPreferredHeight()) { fireResized(); } repaint(); @@ -111,26 +67,29 @@ public class ConsoleGutterComponent extends JComponent implements MouseMotionLis processComponentEvent(new ComponentEvent(this, ComponentEvent.COMPONENT_RESIZED)); } - private void computeAnnotationsSize() { + private void computeMaxAnnotationWidth() { + if (!gutterContentProvider.hasText()) { + maxAnnotationWidth = 0; + return; + } + FontMetrics fontMetrics = editor.getFontMetrics(Font.PLAIN); int lineCount = editor.getDocument().getLineCount(); - GutterContentProvider gutterProvider = gutterContentProvider; - gutterProvider.beforeUiComponentUpdate(editor); + gutterContentProvider.beforeUiComponentUpdate(editor); int gutterSize = 0; for (int i = 0; i < lineCount; i++) { - String text = gutterProvider.getText(i, editor); + String text = gutterContentProvider.getText(i, editor); if (text != null) { gutterSize = Math.max(gutterSize, fontMetrics.stringWidth(text)); } } - annotationGuttersSize = gutterSize; + maxAnnotationWidth = gutterSize + RIGHT_INSET; } @Override public Dimension getPreferredSize() { - int w = annotationGuttersSize; myLastPreferredHeight = editor.getPreferredHeight(); - return new Dimension(w, myLastPreferredHeight); + return new Dimension(maxAnnotationWidth, myLastPreferredHeight); } @Override @@ -138,17 +97,13 @@ public class ConsoleGutterComponent extends JComponent implements MouseMotionLis ((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart(); try { Rectangle clip = g.getClipBounds(); - if (clip.height < 0) { + if (clip.height < 0 || maxAnnotationWidth == 0) { return; } g.setColor(editor.getBackgroundColor()); g.fillRect(clip.x, clip.y, clip.width, clip.height); - if (annotationGuttersSize == 0) { - return; - } - UISettings.setupAntialiasing(g); Graphics2D g2 = (Graphics2D)g; @@ -170,33 +125,29 @@ public class ConsoleGutterComponent extends JComponent implements MouseMotionLis } private void paintAnnotations(Graphics g, Rectangle clip) { - g.setColor(JBColor.blue); - g.setFont(editor.getColorsScheme().getFont(EditorFontType.PLAIN)); - int lineHeight = editor.getLineHeight(); - int startLineNumber = clip.y / lineHeight; - int endLineNumber = (clip.y + clip.height) / lineHeight + 1; - int lastLine = editor.logicalToVisualPosition(new LogicalPosition(getEndLineNumber(), 0)).line; - endLineNumber = Math.min(endLineNumber, lastLine + 1); - if (startLineNumber >= endLineNumber) { + int startLine = clip.y / lineHeight; + int endLine = Math.min(((clip.y + clip.height) / lineHeight) + 1, editor.getVisibleLineCount()); + if (startLine >= endLine) { return; } gutterContentProvider.beforeUiComponentUpdate(editor); - for (int i = startLineNumber; i < endLineNumber; i++) { - int logLine = editor.visualToLogicalPosition(new VisualPosition(i, 0)).line; - String text = gutterContentProvider.getText(logLine, editor); + g.setColor(JBColor.BLUE); + g.setFont(editor.getColorsScheme().getFont(EditorFontType.PLAIN)); + int y = ((startLine + 1) * lineHeight) - editor.getDescent(); + FontMetrics fontMetrics = editor.getFontMetrics(Font.PLAIN); + for (int i = startLine; i < endLine; i++) { + String text = gutterContentProvider.getText(editor.visualToLogicalPosition(new VisualPosition(i, 0)).line, editor); if (text != null) { - g.drawString(text, 0, (i + 1) * lineHeight - editor.getDescent()); + // right-aligned + g.drawString(text, maxAnnotationWidth - RIGHT_INSET - fontMetrics.stringWidth(text), y); } + y += lineHeight; } } - private int getEndLineNumber() { - return Math.max(0, editor.getDocument().getLineCount() - 1); - } - @Override public void mouseDragged(MouseEvent e) { TooltipController.getInstance().cancelTooltips(); @@ -232,7 +183,7 @@ public class ConsoleGutterComponent extends JComponent implements MouseMotionLis } } - private int getLineAtPoint(final Point clickPoint) { + private int getLineAtPoint(@NotNull Point clickPoint) { return editor.yPositionToLogicalLine(clickPoint.y); } } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/execution/console/ConsoleIconGutterComponent.java b/platform/lang-impl/src/com/intellij/execution/console/ConsoleIconGutterComponent.java new file mode 100644 index 000000000000..144ae3f12fcf --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/console/ConsoleIconGutterComponent.java @@ -0,0 +1,96 @@ +package com.intellij.execution.console; + +import com.intellij.ide.ui.UISettings; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.impl.ApplicationImpl; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.VisualPosition; +import com.intellij.openapi.editor.colors.EditorFontType; +import com.intellij.openapi.editor.impl.EditorImpl; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ComponentEvent; + +class ConsoleIconGutterComponent extends JComponent { + static final int ICON_AREA_WIDTH = 15; + + private int myLastPreferredHeight = -1; + private final EditorImpl editor; + + private final GutterContentProvider gutterContentProvider; + + public ConsoleIconGutterComponent(@NotNull Editor editor, @NotNull GutterContentProvider provider) { + this.editor = (EditorImpl)editor; + gutterContentProvider = provider; + } + + public void updateSize() { + if (myLastPreferredHeight != editor.getPreferredHeight()) { + fireResized(); + } + repaint(); + } + + private void fireResized() { + processComponentEvent(new ComponentEvent(this, ComponentEvent.COMPONENT_RESIZED)); + } + + @Override + public Dimension getPreferredSize() { + myLastPreferredHeight = editor.getPreferredHeight(); + return new Dimension(ICON_AREA_WIDTH, myLastPreferredHeight); + } + + @Override + public void paint(Graphics g) { + ((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart(); + try { + Rectangle clip = g.getClipBounds(); + if (clip.height < 0) { + return; + } + + g.setColor(editor.getBackgroundColor()); + g.fillRect(clip.x, clip.y, clip.width, clip.height); + + UISettings.setupAntialiasing(g); + + Graphics2D g2 = (Graphics2D)g; + Object hint = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + if (!UIUtil.isRetina()) { + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + } + + try { + paintAnnotations(g, clip); + } + finally { + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint); + } + } + finally { + ((ApplicationImpl)ApplicationManager.getApplication()).editorPaintFinish(); + } + } + + private void paintAnnotations(Graphics g, Rectangle clip) { + int lineHeight = editor.getLineHeight(); + int startLine = clip.y / lineHeight; + int endLine = Math.min(((clip.y + clip.height) / lineHeight) + 1, editor.getVisibleLineCount()); + if (startLine >= endLine) { + return; + } + + gutterContentProvider.beforeUiComponentUpdate(editor); + + g.setFont(editor.getColorsScheme().getFont(EditorFontType.PLAIN)); + int y = ((startLine + 1) * lineHeight) - editor.getDescent(); + for (int i = startLine; i < endLine; i++) { + gutterContentProvider.drawIcon(editor.visualToLogicalPosition(new VisualPosition(i, 0)).line, g, y, editor); + y += lineHeight; + } + } +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/execution/console/GutterContentProvider.java b/platform/lang-impl/src/com/intellij/execution/console/GutterContentProvider.java index 3a011fe9c5a7..af7d34c0ea59 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/GutterContentProvider.java +++ b/platform/lang-impl/src/com/intellij/execution/console/GutterContentProvider.java @@ -4,6 +4,8 @@ import com.intellij.openapi.editor.Editor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.awt.*; + public abstract class GutterContentProvider { public void beforeUiComponentUpdate(@NotNull Editor editor) { } @@ -11,6 +13,11 @@ public abstract class GutterContentProvider { public void documentCleared(@NotNull Editor editor) { } + public void beforeEvaluate(@NotNull Editor editor) { + } + + public abstract boolean hasText(); + @Nullable public abstract String getText(int line, @NotNull Editor editor); @@ -18,4 +25,6 @@ public abstract class GutterContentProvider { public abstract String getToolTip(int line, @NotNull Editor editor); public abstract void doAction(int line, @NotNull Editor editor); + + public abstract void drawIcon(int line, @NotNull Graphics g, int y, @NotNull Editor editor); } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleBuilder.java b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleBuilder.java index 24d37f649035..75e4520b6758 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleBuilder.java +++ b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleBuilder.java @@ -2,10 +2,18 @@ package com.intellij.execution.console; import com.intellij.execution.process.ProcessHandler; import com.intellij.lang.Language; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.event.DocumentAdapter; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.ex.DocumentBulkUpdateListener; +import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Conditions; +import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -17,16 +25,13 @@ public class LanguageConsoleBuilder { private LanguageConsoleView myConsoleView; private Condition myExecutionEnabled = Conditions.alwaysTrue(); - @Nullable - private GutterContentProvider myGutterProvider; - public LanguageConsoleBuilder(@NotNull LanguageConsoleView consoleView) { myConsole = consoleView.getConsole(); myConsoleView = consoleView; } public LanguageConsoleBuilder(@NotNull Project project, @NotNull Language language) { - myConsole = new LanguageConsoleImpl(project, language.getDisplayName() + " Console", language, false); + myConsole = new MyLanguageConsole(project, language); } public LanguageConsoleBuilder processHandler(@NotNull ProcessHandler processHandler) { @@ -50,7 +55,7 @@ public class LanguageConsoleBuilder { } public LanguageConsoleBuilder historyAnnotation(@Nullable GutterContentProvider provider) { - myGutterProvider = provider; + ((MyLanguageConsole)myConsole).gutterContentProvider = provider; return this; } @@ -64,19 +69,6 @@ public class LanguageConsoleBuilder { myConsole.setShowSeparatorLine(false); myConsole.initComponents(); - if (myGutterProvider != null) { - // todo consider to fix platform - myConsole.getConsoleEditor().getSettings().setAdditionalLinesCount(1); - - EditorEx editor = myConsole.getHistoryViewer(); - - JScrollPane scrollPane = editor.getScrollPane(); - JPanel panel = new JPanel(new BorderLayout()); - panel.add(scrollPane.getViewport().getView(), BorderLayout.CENTER); - panel.add(new ConsoleGutterComponent(editor, myGutterProvider, myConsole), BorderLayout.LINE_END); - scrollPane.setViewportView(panel); - } - ensureConsoleViewCreated(); return myConsoleView; } @@ -93,4 +85,115 @@ public class LanguageConsoleBuilder { return !myProcessHandler.isProcessTerminated(); } } + + private static class GutteredEditorPanel extends JPanel { + private final EditorEx editor; + + public GutteredEditorPanel(EditorEx editor) { + super(new BorderLayout()); + + this.editor = editor; + } + + @Override + public void paint(Graphics g) { + super.paint(g); + + Rectangle clip = g.getClipBounds(); + if (clip.height < 0) { + return; + } + + int lineHeight = editor.getLineHeight(); + int startLine = clip.y / lineHeight; + int endLine = Math.min(((clip.y + clip.height) / lineHeight) + 1, ((EditorImpl)editor).getVisibleLineCount()); + if (startLine >= endLine) { + return; + } + + int y = ((startLine + 1) * lineHeight); + g.setColor(editor.getColorsScheme().getColor(EditorColors.INDENT_GUIDE_COLOR)); + for (int i = startLine; i < endLine; i++) { + g.drawLine(ConsoleIconGutterComponent.ICON_AREA_WIDTH, y, clip.width, y); + y += lineHeight; + } + } + } + + private static class MyLanguageConsole extends LanguageConsoleImpl { + @Nullable + private GutterContentProvider gutterContentProvider; + + public MyLanguageConsole(@NotNull Project project, @NotNull Language language) { + super(project, language.getDisplayName() + " Console", language, false); + } + + @Override + protected void setupEditorDefault(@NotNull EditorEx editor) { + super.setupEditorDefault(editor); + + if (editor == getConsoleEditor()) { + // todo consider to fix platform + editor.getSettings().setAdditionalLinesCount(1); + } + else if (gutterContentProvider != null) { + JScrollPane scrollPane = editor.getScrollPane(); + JPanel panel = new GutteredEditorPanel(editor); + + final ConsoleIconGutterComponent lineStartGutter = new ConsoleIconGutterComponent(editor, gutterContentProvider); + panel.add(lineStartGutter, BorderLayout.LINE_START); + + panel.add(scrollPane.getViewport().getView(), BorderLayout.CENTER); + + final ConsoleGutterComponent lineEndGutter = new ConsoleGutterComponent(editor, gutterContentProvider); + panel.add(lineEndGutter, BorderLayout.LINE_END); + + scrollPane.setViewportView(panel); + + getProject().getMessageBus().connect(this).subscribe(DocumentBulkUpdateListener.TOPIC, new DocumentBulkUpdateListener.Adapter() { + @Override + public void updateFinished(@NotNull Document document) { + if (document.getTextLength() == 0) { + gutterContentProvider.documentCleared(getHistoryViewer()); + } + lineStartGutter.updateSize(); + lineEndGutter.updateSize(); + } + }); + + editor.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + public void documentChanged(DocumentEvent event) { + EditorEx editor = getHistoryViewer(); + DocumentEx document = editor.getDocument(); + if (document.isInBulkUpdate()) { + return; + } + + if (document.getTextLength() > 0) { + int startDocLine = document.getLineNumber(event.getOffset()); + int endDocLine = document.getLineNumber(event.getOffset() + event.getNewLength()); + if (event.getOldLength() > event.getNewLength() || startDocLine != endDocLine || StringUtil.indexOf(event.getOldFragment(), '\n') != -1) { + lineStartGutter.updateSize(); + lineEndGutter.updateSize(); + } + } + else if (event.getOldLength() > 0) { + gutterContentProvider.documentCleared(editor); + } + } + }); + } + } + + @Override + protected void doAddPromptToHistory() { + if (gutterContentProvider == null) { + super.doAddPromptToHistory(); + } + else { + gutterContentProvider.beforeEvaluate(getHistoryViewer()); + } + } + } } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java index d385a9a7ec06..fed2716b32b1 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java @@ -185,8 +185,7 @@ public class LanguageConsoleImpl implements Disposable, TypeSafeDataProvider { @Override public void componentResized(ComponentEvent e) { if (myForceScrollToEnd.getAndSet(false)) { - final JScrollBar scrollBar = myHistoryViewer.getScrollPane().getVerticalScrollBar(); - scrollBar.setValue(scrollBar.getMaximum()); + scrollHistoryToEnd(); } } @@ -299,7 +298,7 @@ public class LanguageConsoleImpl implements Disposable, TypeSafeDataProvider { queueUiUpdate(true); } - private void setupEditorDefault(@NotNull EditorEx editor) { + protected void setupEditorDefault(@NotNull EditorEx editor) { ConsoleViewUtil.setupConsoleEditor(editor, false, false); editor.getContentComponent().setFocusCycleRoot(false); editor.setHorizontalScrollbarVisible(false); @@ -410,30 +409,26 @@ public class LanguageConsoleImpl implements Disposable, TypeSafeDataProvider { } protected void addTextToHistory(@Nullable CharSequence text, @Nullable TextAttributes attributes) { - if (StringUtil.isEmpty(text)) { + if (StringUtil.isEmpty(text) || attributes == null) { return; } Document history = myHistoryViewer.getDocument(); MarkupModel markupModel = DocumentMarkupModel.forDocument(history, myProject, true); int offset = appendToHistoryDocument(history, text); - if (attributes == null) return; markupModel.addRangeHighlighter(offset, offset + text.length(), HighlighterLayer.SYNTAX, attributes, HighlighterTargetArea.EXACT_RANGE); } - public String addCurrentToHistory(final TextRange textRange, final boolean erase, final boolean preserveMarkup) { + public String addCurrentToHistory(@NotNull TextRange textRange, boolean erase, boolean preserveMarkup) { return addToHistoryInner(textRange, myConsoleEditor, erase, preserveMarkup); } - public String addToHistory(final TextRange textRange, final EditorEx editor, final boolean preserveMarkup) { + public String addToHistory(@NotNull TextRange textRange, @NotNull EditorEx editor, boolean preserveMarkup) { return addToHistoryInner(textRange, editor, false, preserveMarkup); } @NotNull - protected String addToHistoryInner(@NotNull final TextRange textRange, - @NotNull final EditorEx editor, - final boolean erase, - final boolean preserveMarkup) { + protected String addToHistoryInner(@NotNull final TextRange textRange, @NotNull final EditorEx editor, boolean erase, final boolean preserveMarkup) { String result = ApplicationManager.getApplication().runReadAction(new Computable() { @Override public String compute() { @@ -462,13 +457,15 @@ public class LanguageConsoleImpl implements Disposable, TypeSafeDataProvider { private void scrollHistoryToEnd() { final int lineCount = myHistoryViewer.getDocument().getLineCount(); - if (lineCount == 0) return; + if (lineCount == 0) { + return; + } myHistoryViewer.getCaretModel().moveToOffset(myHistoryViewer.getDocument().getLineStartOffset(lineCount - 1), false); myHistoryViewer.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); } @NotNull - protected String addTextRangeToHistory(@NotNull TextRange textRange, @NotNull final EditorEx consoleEditor, boolean preserveMarkup) { + protected String addTextRangeToHistory(@NotNull TextRange textRange, @NotNull EditorEx consoleEditor, boolean preserveMarkup) { final Document history = myHistoryViewer.getDocument(); final MarkupModel markupModel = DocumentMarkupModel.forDocument(history, myProject, true); doAddPromptToHistory(); @@ -667,17 +664,21 @@ public class LanguageConsoleImpl implements Disposable, TypeSafeDataProvider { @Override public void layoutContainer(@NotNull final Container parent) { final int componentCount = parent.getComponentCount(); - if (componentCount == 0) return; + if (componentCount == 0) { + return; + } + final EditorEx history = myHistoryViewer; final EditorEx editor = componentCount == 2 ? myConsoleEditor : null; - if (editor == null) { parent.getComponent(0).setBounds(parent.getBounds()); return; } final Dimension panelSize = parent.getSize(); - if (panelSize.getHeight() <= 0) return; + if (panelSize.getHeight() <= 0) { + return; + } final Dimension historySize = history.getContentSize(); final Dimension editorSize = editor.getContentSize(); final Dimension newEditorSize = new Dimension(); @@ -690,7 +691,9 @@ public class LanguageConsoleImpl implements Disposable, TypeSafeDataProvider { history.getSettings().setAdditionalColumnsCount(2 + (width - historySize.width) / EditorUtil.getSpaceWidth(Font.PLAIN, history)); // deal with height - if (historySize.width == 0) historySize.height = 0; + if (historySize.width == 0) { + historySize.height = 0; + } final int minHistorySize = historySize.height > 0 ? 2 * history.getLineHeight() + (myShowSeparatorLine ? SEPARATOR_THICKNESS : 0) : 0; final int minEditorSize = editor.isViewer() ? 0 : editor.getLineHeight(); final int editorPreferred = editor.isViewer() ? 0 : Math.max(minEditorSize, editorSize.height); diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java b/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java index dea1e5a046ab..626f18441687 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java @@ -65,7 +65,7 @@ public class RunnerAndConfigurationSettingsImpl implements JDOMExternalizable, C @NonNls private static final String EDIT_BEFORE_RUN = "editBeforeRun"; @NonNls - private static final String SINGLETON = "singleton"; + public static final String SINGLETON = "singleton"; /** for compatibility */ diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java index 7dbdba79e694..c6200ebf94c3 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java @@ -328,7 +328,7 @@ public class InjectedLanguageUtil { // modification of cachedInjectedDocuments must be under PsiLock only ConcurrentList injected = hostPsiFile.getUserData(INJECTED_DOCS_KEY); if (injected == null) { - injected = ((UserDataHolderEx)hostPsiFile).putUserDataIfAbsent(INJECTED_DOCS_KEY, ContainerUtil.createLockFreeCopyOnWriteList()); + injected = ((UserDataHolderEx)hostPsiFile).putUserDataIfAbsent(INJECTED_DOCS_KEY, ContainerUtil.createConcurrentList()); } return injected; } diff --git a/platform/platform-api/src/com/intellij/execution/process/CapturingProcessHandler.java b/platform/platform-api/src/com/intellij/execution/process/CapturingProcessHandler.java index 2452db7cdd83..fb8c9a461d88 100644 --- a/platform/platform-api/src/com/intellij/execution/process/CapturingProcessHandler.java +++ b/platform/platform-api/src/com/intellij/execution/process/CapturingProcessHandler.java @@ -18,6 +18,7 @@ package com.intellij.execution.process; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; import org.jetbrains.annotations.NotNull; import java.nio.charset.Charset; @@ -105,4 +106,24 @@ public class CapturingProcessHandler extends OSProcessHandler { } return super.getCharset(); } + + @NotNull + public ProcessOutput runProcessWithProgressIndicator(@NotNull ProgressIndicator indicator) { + startNotify(); + while (!waitFor(100)) { + if (indicator.isCanceled()) { + if (!isProcessTerminating() && !isProcessTerminated()) { + destroyProcess(); + } + break; + } + } + if (waitFor()) { + myOutput.setExitCode(getProcess().exitValue()); + } + else { + LOG.info("runProcess: exit value unavailable"); + } + return myOutput; + } } \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/openapi/ui/LoadingDecorator.java b/platform/platform-api/src/com/intellij/openapi/ui/LoadingDecorator.java index 7509200752c3..733a84b81c4b 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/LoadingDecorator.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/LoadingDecorator.java @@ -62,8 +62,8 @@ public class LoadingDecorator { Disposer.register(parent, myFadeOutAnimator); - myPane.add(content, 0, JLayeredPane.DEFAULT_LAYER); - myPane.add(myLoadingLayer, 1, JLayeredPane.DRAG_LAYER); + myPane.add(content, JLayeredPane.DEFAULT_LAYER, 0); + myPane.add(myLoadingLayer, JLayeredPane.DRAG_LAYER, 1); Disposer.register(parent, myLoadingLayer.myProgress); } diff --git a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java index 4e9a9b948d5b..cface15102e0 100644 --- a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java +++ b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -185,7 +185,6 @@ public class JobLauncherImpl extends JobLauncher { @Override protected void setRawResult(Void value) { - } @Override diff --git a/platform/platform-impl/src/com/intellij/ide/util/TipAndTrickBean.java b/platform/platform-impl/src/com/intellij/ide/util/TipAndTrickBean.java new file mode 100644 index 000000000000..fcaa09c22ae0 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/util/TipAndTrickBean.java @@ -0,0 +1,55 @@ +/* + * 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.intellij.ide.util; + +import com.intellij.openapi.extensions.AbstractExtensionPointBean; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.util.Comparing; +import com.intellij.util.xmlb.annotations.Attribute; +import org.jetbrains.annotations.Nullable; + +/** + * @author gregsh + */ +public class TipAndTrickBean extends AbstractExtensionPointBean { + public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.tipAndTrick"); + + @Attribute("file") + public String myFileName; + + @Attribute("feature-id") + public String myFeatureId; + + public String getFileName() { + return myFileName; + } + + public String getFeatureId() { + return myFeatureId; + } + + @Nullable + public static TipAndTrickBean findByFileName(String tipFileName) { + for (TipAndTrickBean tip : Extensions.getExtensions(EP_NAME)) { + if (Comparing.equal(tipFileName, tip.getFileName())) { + return tip; + } + } + return null; + } +} + diff --git a/platform/platform-impl/src/com/intellij/ide/util/TipPanel.java b/platform/platform-impl/src/com/intellij/ide/util/TipPanel.java index 8ccdda223cb6..e44be06c8b41 100644 --- a/platform/platform-impl/src/com/intellij/ide/util/TipPanel.java +++ b/platform/platform-impl/src/com/intellij/ide/util/TipPanel.java @@ -15,18 +15,17 @@ */ package com.intellij.ide.util; -import com.intellij.featureStatistics.FeatureDescriptor; -import com.intellij.featureStatistics.ProductivityFeaturesProvider; import com.intellij.icons.AllIcons; +import com.intellij.ide.BrowserUtil; import com.intellij.ide.GeneralSettings; import com.intellij.ide.IdeBundle; import com.intellij.openapi.application.ApplicationNamesInfo; -import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.extensions.Extensions; import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.components.JBLabel; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; -import org.jdom.Document; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; import javax.swing.*; import javax.swing.event.HyperlinkEvent; @@ -35,20 +34,16 @@ import javax.swing.text.html.HTMLEditorKit; import java.awt.*; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; -import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; +import java.util.List; public class TipPanel extends JPanel { private static final int DEFAULT_WIDTH = 400; private static final int DEFAULT_HEIGHT = 200; - private final JCheckBox myCheckBox; - private final JEditorPane browser; - private final ArrayList myTipPaths = new ArrayList(); - private final HashMap> myPathsToProviderMap = new HashMap>(); - @NonNls - private static final String ELEMENT_TIP = "tip"; - @NonNls - private static final String ATTRIBUTE_FILE = "file"; + + private final JEditorPane myBrowserPanel; + private final JLabel myPoweredByLabel; + private final List myTips = ContainerUtil.newArrayList(); public TipPanel() { setLayout(new BorderLayout()); @@ -63,45 +58,42 @@ public class TipPanel extends JPanel { jpanel.add(jlabel1, BorderLayout.CENTER); jpanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); add(jpanel, BorderLayout.NORTH); - browser = new JEditorPane(); - browser.setEditable(false); - browser.setEditorKit(new HTMLEditorKit()); - browser.setBackground(UIUtil.getTextFieldBackground()); - browser.addHyperlinkListener( + myBrowserPanel = new JEditorPane(); + myBrowserPanel.setEditable(false); + myBrowserPanel.setEditorKit(new HTMLEditorKit()); + myBrowserPanel.setBackground(UIUtil.getTextFieldBackground()); + myBrowserPanel.addHyperlinkListener( new HyperlinkListener() { public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - //TODO: Open url in browser + BrowserUtil.browse(e.getURL()); } } } ); - JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(browser); + JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myBrowserPanel); add(scrollPane, BorderLayout.CENTER); - myCheckBox = new JCheckBox(IdeBundle.message("checkbox.show.tips.on.startup"), true); - myCheckBox.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); + + JPanel southPanel = new JPanel(new BorderLayout()); + JCheckBox showOnStartCheckBox = new JCheckBox(IdeBundle.message("checkbox.show.tips.on.startup"), true); + showOnStartCheckBox.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); final GeneralSettings settings = GeneralSettings.getInstance(); - myCheckBox.setSelected(settings.showTipsOnStartup()); - myCheckBox.addItemListener(new ItemListener() { + showOnStartCheckBox.setSelected(settings.showTipsOnStartup()); + showOnStartCheckBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { settings.setShowTipsOnStartup(e.getStateChange() == ItemEvent.SELECTED); } }); - add(myCheckBox, BorderLayout.SOUTH); - try { - readTips("/tips/tips.xml"); - readTips("/tips/IdeSpecificTips.xml"); - } - catch (Exception exception) {// - } + southPanel.add(showOnStartCheckBox, BorderLayout.WEST); - for (ProductivityFeaturesProvider provider : ProductivityFeaturesProvider.EP_NAME.getExtensions()) { - final FeatureDescriptor[] descriptors = provider.getFeatureDescriptors(); - for (int j = 0; descriptors != null && j < descriptors.length; j++) { - FeatureDescriptor descriptor = descriptors[j]; - myPathsToProviderMap.put(descriptor.getTipFileName(), descriptor.getProvider()); - } - } + myPoweredByLabel = new JBLabel(); + myPoweredByLabel.setHorizontalAlignment(SwingConstants.RIGHT); + myPoweredByLabel.setForeground(SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES.getFgColor()); + + southPanel.add(myPoweredByLabel, BorderLayout.EAST); + add(southPanel, BorderLayout.SOUTH); + + Collections.addAll(myTips, Extensions.getExtensions(TipAndTrickBean.EP_NAME)); } public Dimension getPreferredSize() { @@ -109,57 +101,49 @@ public class TipPanel extends JPanel { } public void prevTip() { - if (myTipPaths.size() == 0) { - browser.setText(IdeBundle.message("error.tips.not.found", ApplicationNamesInfo.getInstance().getFullProductName())); + if (myTips.size() == 0) { + myBrowserPanel.setText(IdeBundle.message("error.tips.not.found", ApplicationNamesInfo.getInstance().getFullProductName())); return; } final GeneralSettings settings = GeneralSettings.getInstance(); int lastTip = settings.getLastTip(); - final String path; + final TipAndTrickBean tip; lastTip--; if (lastTip <= 0) { - path = myTipPaths.get(myTipPaths.size() - 1); - lastTip = myTipPaths.size(); + tip = myTips.get(myTips.size() - 1); + lastTip = myTips.size(); } else { - path = myTipPaths.get(lastTip - 1); + tip = myTips.get(lastTip - 1); } - setTip(path, lastTip, browser, settings); + setTip(tip, lastTip, myBrowserPanel, settings); } - private void setTip (String path, int lastTip, JEditorPane browser, GeneralSettings settings) { - TipUIUtil.openTipInBrowser(path, browser, myPathsToProviderMap.get(path)); + private void setTip (TipAndTrickBean tip, int lastTip, JEditorPane browser, GeneralSettings settings) { + TipUIUtil.openTipInBrowser(tip, browser); + myPoweredByLabel.setText(TipUIUtil.getPoweredByText(tip)); settings.setLastTip(lastTip); } public void nextTip() { - if (myTipPaths.size() == 0) { - browser.setText(IdeBundle.message("error.tips.not.found", ApplicationNamesInfo.getInstance().getFullProductName())); + if (myTips.size() == 0) { + myBrowserPanel.setText(IdeBundle.message("error.tips.not.found", ApplicationNamesInfo.getInstance().getFullProductName())); return; } GeneralSettings settings = GeneralSettings.getInstance(); int lastTip = settings.getLastTip(); - String path; + TipAndTrickBean tip; lastTip++; - if (lastTip - 1 >= myTipPaths.size()) { - path = myTipPaths.get(0); + if (lastTip - 1 >= myTips.size()) { + tip = myTips.get(0); lastTip = 1; } else { - path = myTipPaths.get(lastTip - 1); + tip = myTips.get(lastTip - 1); } - setTip(path, lastTip, browser, settings); - } - - private void readTips(String tipsURL) throws Exception { - final Document document = JDOMUtil.loadDocument(getClass().getResource(tipsURL).openStream()); - - for (Object o : document.getRootElement().getChildren(ELEMENT_TIP)) { - Element tip = (Element)o; - myTipPaths.add(tip.getAttributeValue(ATTRIBUTE_FILE)); - } + setTip(tip, lastTip, myBrowserPanel, settings); } } diff --git a/platform/platform-impl/src/com/intellij/ide/util/TipUIUtil.java b/platform/platform-impl/src/com/intellij/ide/util/TipUIUtil.java index 0e531bfc1fc9..0fb2bbef99e0 100644 --- a/platform/platform-impl/src/com/intellij/ide/util/TipUIUtil.java +++ b/platform/platform-impl/src/com/intellij/ide/util/TipUIUtil.java @@ -16,17 +16,23 @@ package com.intellij.ide.util; import com.intellij.ide.IdeBundle; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.openapi.actionSystem.KeyboardShortcut; import com.intellij.openapi.actionSystem.Shortcut; import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.keymap.Keymap; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.keymap.impl.DefaultKeymap; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ObjectUtils; import com.intellij.util.ResourceUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.imageio.ImageIO; @@ -46,7 +52,25 @@ public class TipUIUtil { private TipUIUtil() { } - public static void openTipInBrowser(String tipPath, JEditorPane browser, Class providerClass) { + @NotNull + public static String getPoweredByText(@NotNull TipAndTrickBean tip) { + PluginDescriptor descriptor = tip.getPluginDescriptor(); + return descriptor instanceof IdeaPluginDescriptor && + !PluginManagerCore.CORE_PLUGIN_ID.equals(descriptor.getPluginId().getIdString()) ? + ((IdeaPluginDescriptor)descriptor).getName() : ""; + } + + public static void openTipInBrowser(String tipFileName, JEditorPane browser, Class providerClass) { + TipAndTrickBean tip = TipAndTrickBean.findByFileName(tipFileName); + if (tip == null && StringUtil.isNotEmpty(tipFileName)) { + tip = new TipAndTrickBean(); + tip.myFileName = tipFileName; + } + openTipInBrowser(tip, browser); + } + + public static void openTipInBrowser(@Nullable TipAndTrickBean tip, JEditorPane browser) { + if (tip == null) return; /* TODO: detect that file is not present if (!file.exists()) { browser.read(new StringReader("Tips for '" + feature.getDisplayName() + "' not found. Make sure you installed IntelliJ IDEA correctly."), null); @@ -54,18 +78,20 @@ public class TipUIUtil { } */ try { - if (tipPath == null) return; - if (providerClass == null) providerClass = TipUIUtil.class; - URL url = ResourceUtil.getResource(providerClass, "/tips/", tipPath); + PluginDescriptor pluginDescriptor = tip.getPluginDescriptor(); + ClassLoader tipLoader = pluginDescriptor == null ? TipUIUtil.class.getClassLoader() : + ObjectUtils.notNull(pluginDescriptor.getPluginClassLoader(), TipUIUtil.class.getClassLoader()); + + URL url = ResourceUtil.getResource(tipLoader, "/tips/", tip.getFileName()); if (url == null) { - setCantReadText(browser, tipPath); + setCantReadText(browser, tip); return; } StringBuffer text = new StringBuffer(ResourceUtil.loadText(url)); updateShortcuts(text); - updateImages(text, providerClass); + updateImages(text, tipLoader); String replaced = text.toString().replace("&productName;", ApplicationNamesInfo.getInstance().getFullProductName()); replaced = replaced.replace("&majorVersion;", ApplicationInfo.getInstance().getMajorVersion()); replaced = replaced.replace("&minorVersion;", ApplicationInfo.getInstance().getMinorVersion()); @@ -75,11 +101,11 @@ public class TipUIUtil { browser.read(new StringReader(replaced), url); } catch (IOException e) { - setCantReadText(browser, tipPath); + setCantReadText(browser, tip); } } - private static void setCantReadText(JEditorPane browser, String missingFile) { + private static void setCantReadText(JEditorPane browser, TipAndTrickBean missingFile) { try { browser.read(new StringReader( IdeBundle.message("error.unable.to.read.tip.of.the.day", missingFile, ApplicationNamesInfo.getInstance().getFullProductName())), null); @@ -88,7 +114,7 @@ public class TipUIUtil { } } - private static void updateImages(StringBuffer text, Class providerClass) { + private static void updateImages(StringBuffer text, ClassLoader tipLoader) { final boolean dark = UIUtil.isUnderDarcula(); final boolean retina = UIUtil.isRetina(); // if (!dark && !retina) { @@ -109,7 +135,7 @@ public class TipUIUtil { String path = img.substring(srcIndex + 5, endIndex); if (!path.endsWith("_dark") && !path.endsWith("@2x")) { path += suffix + ".png"; - URL url = ResourceUtil.getResource(providerClass, "/tips/", path); + URL url = ResourceUtil.getResource(tipLoader, "/tips/", path); if (url != null) { String newImgTag = " process(String text1, String text2) throws FilesTooBigForDiffException { + if (text1.isEmpty() || text2.isEmpty()) { + return new DummyDiffFragmentsProcessor().process(text1, text2); + } + DiffFragment[] woFormattingBlocks = myDiffPolicy.buildFragments(text1, text2); DiffFragment[] step1lineFragments = new DiffCorrection.TrueLineBlocks(myComparisonPolicy).correctAndNormalize(woFormattingBlocks); ArrayList lineBlocks = new DiffFragmentsProcessor().process(step1lineFragments); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java index 331b31a8ca17..cb0aee733817 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java @@ -384,6 +384,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, @Override public void runBatchCaretOperation(@NotNull Runnable runnable) { + myEditor.assertIsDispatchThread(); doWithCaretMerging(runnable); } @@ -402,8 +403,8 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, } CaretImpl currCaret = it.next(); if (prevCaret != null && (currCaret.getVisualPosition().equals(prevCaret.getVisualPosition()) - || Math.max(currCaret.getSelectionStart(), prevCaret.getSelectionStart()) - < Math.min(currCaret.getSelectionEnd(), prevCaret.getSelectionEnd()))) { + || regionsIntersect(currCaret.getSelectionStart(), currCaret.getSelectionEnd(), + prevCaret.getSelectionStart(), prevCaret.getSelectionEnd()))) { int newSelectionStart = Math.min(currCaret.getSelectionStart(), prevCaret.getSelectionStart()); int newSelectionEnd = Math.max(currCaret.getSelectionEnd(), prevCaret.getSelectionEnd()); CaretImpl toRetain, toRemove; @@ -428,6 +429,12 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, } } + private static boolean regionsIntersect(int firstStart, int firstEnd, int secondStart, int secondEnd) { + return firstStart < secondStart && firstEnd > secondStart + || firstStart > secondStart && firstStart < secondEnd + || firstStart == secondStart && secondEnd > secondStart && firstEnd > firstStart; + } + void doWithCaretMerging(Runnable runnable) { if (myPerformCaretMergingAfterCurrentOperation) { runnable.run(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 178bac6246a8..fc68bf4a1ffa 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -3645,7 +3645,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi * lines (considering that single logical document line may be represented on multiple visual lines because of * soft wraps appliance) minus number of folded lines */ - int getVisibleLineCount() { + public int getVisibleLineCount() { return getVisibleLogicalLinesCount() + getSoftWrapModel().getSoftWrapsIntroducedLinesNumber(); } diff --git a/platform/platform-resources-en/src/messages/IdeBundle.properties b/platform/platform-resources-en/src/messages/IdeBundle.properties index 9263d4f083ea..0c44be2be4f0 100644 --- a/platform/platform-resources-en/src/messages/IdeBundle.properties +++ b/platform/platform-resources-en/src/messages/IdeBundle.properties @@ -772,7 +772,7 @@ action.mark=Mark action.do.not.mark=Do Not Mark error.source.directory.should.be.under.module.content.root.directory=Source directory should be under module content root directory progress.searching.for.sources=Searching for sources in {0}. Please wait. -prompt.select.source.directory=Select source directory +prompt.select.source.directory=Select Source Directory prompt.new.project.file.name=Enter a file name to create a new {0} {1} prompt.enter.project.file.location=Enter {0} file location directory.project.file.directory=The {0} file directory\n diff --git a/platform/platform-resources-en/src/misc/registry.properties b/platform/platform-resources-en/src/misc/registry.properties index 581374e2dd37..fd9d8fc883a6 100644 --- a/platform/platform-resources-en/src/misc/registry.properties +++ b/platform/platform-resources-en/src/misc/registry.properties @@ -328,6 +328,7 @@ junit_sm_runner=false testng_sm_runner=false show.flex.debug.design.view=false git.new.log=true +diff.patience.alg=false svn.use.terminal=false diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index 4ab86f41ee1b..eaff367e3eb1 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -215,5 +215,6 @@ + diff --git a/platform/platform-resources/src/META-INF/XmlPlugin.xml b/platform/platform-resources/src/META-INF/XmlPlugin.xml index 470c8ada1988..88c3d8ba801f 100644 --- a/platform/platform-resources/src/META-INF/XmlPlugin.xml +++ b/platform/platform-resources/src/META-INF/XmlPlugin.xml @@ -102,6 +102,8 @@ + + diff --git a/platform/platform-resources/src/brokenPlugins.txt b/platform/platform-resources/src/brokenPlugins.txt index 9116bbd62b98..a67cc57567c3 100644 --- a/platform/platform-resources/src/brokenPlugins.txt +++ b/platform/platform-resources/src/brokenPlugins.txt @@ -2,10 +2,12 @@ // Each line contains plugin ID and list of versions that are broken. // If plugin name or version contains a space you can quote it like in command line. -NodeJS 134.680 134.31 134.307 134.262 134.198 134.125 +NodeJS 134.1145 134.1081 134.1039 134.985 134.680 134.31 134.307 134.262 134.198 134.125 com.jetbrains.php 133.679 133.51 133.326 131.98 131.374 131.332 131.235 131.205 130.1639 130.1481 130.1176 129.91 129.814 129.672 129.362 127.67 127.100 126.334 123.66 122.875 121.62 121.390 121.215 121.12 com.jetbrains.lang.ejs 131.17 131.12 com.jetbrains.twig 133.51 130.1639 +//org.jetbrains.plugins.ruby 6.0.0.20140207 +Pythonid 3.1 Karma 134.686 134.31 org.intellij.scala 0.32.512 org.jetbrains.kannotator 0.2.420 diff --git a/platform/platform-tests/testData/editor/multiCaret/$SelectAll-after.txt b/platform/platform-tests/testData/editor/multiCaret/$SelectAll-after.txt new file mode 100644 index 000000000000..cceb84444edb --- /dev/null +++ b/platform/platform-tests/testData/editor/multiCaret/$SelectAll-after.txt @@ -0,0 +1,2 @@ +some text +some other text \ No newline at end of file diff --git a/platform/platform-tests/testData/editor/multiCaret/$SelectAll-before.txt b/platform/platform-tests/testData/editor/multiCaret/$SelectAll-before.txt new file mode 100644 index 000000000000..c117ff50c815 --- /dev/null +++ b/platform/platform-tests/testData/editor/multiCaret/$SelectAll-before.txt @@ -0,0 +1,2 @@ +some text +some other text \ No newline at end of file diff --git a/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretAbove-after.txt b/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretAbove-after.txt new file mode 100644 index 000000000000..7a9d37447149 --- /dev/null +++ b/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretAbove-after.txt @@ -0,0 +1,3 @@ +some text +some other text +another line of text \ No newline at end of file diff --git a/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretAbove-before.txt b/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretAbove-before.txt new file mode 100644 index 000000000000..caad4cba166c --- /dev/null +++ b/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretAbove-before.txt @@ -0,0 +1,3 @@ +some text +some other text +another line of text \ No newline at end of file diff --git a/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretBelow-after.txt b/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretBelow-after.txt new file mode 100644 index 000000000000..a24e0341fed4 --- /dev/null +++ b/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretBelow-after.txt @@ -0,0 +1,3 @@ +some text +some other text +another line of text \ No newline at end of file diff --git a/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretBelow-before.txt b/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretBelow-before.txt new file mode 100644 index 000000000000..3758a93e2c92 --- /dev/null +++ b/platform/platform-tests/testData/editor/multiCaret/EditorCloneCaretBelow-before.txt @@ -0,0 +1,3 @@ +some text +some other text +another line of text \ No newline at end of file diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretStateRestoreTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretStateRestoreTest.java new file mode 100644 index 000000000000..d915753db54c --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretStateRestoreTest.java @@ -0,0 +1,66 @@ +/* + * 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.intellij.openapi.editor; + +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.impl.DocumentImpl; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.HeavyFileEditorManagerTestCase; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.fileTypes.PlainTextFileType; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.testFramework.EditorTestUtil; +import org.jetbrains.annotations.NotNull; + +public class EditorMultiCaretStateRestoreTest extends HeavyFileEditorManagerTestCase { + public void testRestoreState() throws Exception { + EditorTestUtil.enableMultipleCarets(); + try { + String text = "some text\n" + + "some other text\n" + + "another line"; + PsiFile psiFile = myFixture.configureByText(PlainTextFileType.INSTANCE, text); + VirtualFile virtualFile = psiFile.getVirtualFile(); + assertNotNull(virtualFile); + myManager.openFile(virtualFile, false); + myManager.closeAllFiles(); + FileEditor[] fileEditors = myManager.openFile(virtualFile, false); + assertNotNull(fileEditors); + assertEquals(1, fileEditors.length); + Editor editor = ((TextEditor)fileEditors[0]).getEditor(); + + verifyEditorState(editor, text); + } + finally { + EditorTestUtil.disableMultipleCarets(); + } + } + + private static void verifyEditorState(Editor editor, String textWithMarkup) { + final Document document = new DocumentImpl(textWithMarkup); + EditorTestUtil.CaretsState caretAndSelectionState = new WriteCommandAction(null) { + @Override + protected void run(@NotNull Result result) throws Throwable { + result.setResult(EditorTestUtil.extractCaretAndSelectionMarkers(document)); + } + }.execute().getResultObject(); + assertEquals(document.getCharsSequence().toString(), editor.getDocument().getText()); + EditorTestUtil.verifyCaretAndSelectionState(editor, caretAndSelectionState); + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java index e93fd8c7882a..68a80b3493c1 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java @@ -17,6 +17,11 @@ package com.intellij.openapi.editor; import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.editor.impl.AbstractEditorTest; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileEditor.impl.EditorHistoryManager; +import com.intellij.openapi.project.ex.ProjectManagerEx; +import com.intellij.openapi.project.impl.ProjectManagerImpl; import com.intellij.testFramework.EditorTestUtil; import com.intellij.testFramework.TestFileType; import com.intellij.testFramework.fixtures.EditorScrollingFixture; @@ -46,19 +51,19 @@ public class EditorMultiCaretTest extends AbstractEditorTest { checkResultByText("some text\n" + "another line"); - mouse().alt().shift().clickAt(0,8); // alt-shift-click in existing selection + mouse().alt().shift().clickAt(0, 8); // alt-shift-click in existing selection checkResultByText("some text\n" + "another line"); - mouse().alt().shift().clickAt(0,6); // alt-shift-click at existing caret with selection + mouse().alt().shift().clickAt(0, 6); // alt-shift-click at existing caret with selection checkResultByText("some text\n" + "another line"); - mouse().alt().shift().clickAt(1,1); // alt-shift-click at the sole caret + mouse().alt().shift().clickAt(1, 1); // alt-shift-click at the sole caret checkResultByText("some text\n" + "another line"); - mouse().alt().shift().clickAt(0,30); // alt-shift-click in virtual space + mouse().alt().shift().clickAt(0, 30); // alt-shift-click in virtual space checkResultByText("some text\n" + "another line"); @@ -142,4 +147,66 @@ public class EditorMultiCaretTest extends AbstractEditorTest { "long line\n" + "line"); } + + public void testTyping() throws Exception { + init("some text\n" + + "some other text\n" + + "another line", + TestFileType.TEXT + ); + type('A'); + checkResultByText("someA textA\n" + + "some A A\n" + + "A line"); + } + + public void testCopyPaste() throws Exception { + init("one two \n" + + "three four ", + TestFileType.TEXT); + executeAction("EditorCopy"); + executeAction("EditorLineEnd"); + executeAction("EditorPaste"); + checkResultByText("one twoone \n" + + "three fourthree "); + } + + public void testCutAndPaste() throws Exception { + init("one two \n" + + "three four ", + TestFileType.TEXT + ); + executeAction("EditorCut"); + executeAction("EditorLineEnd"); + executeAction("EditorPaste"); + checkResultByText(" twoone \n" + + " fourthree "); + } + + public void testPasteSingleItem() throws Exception { + init("one two \n" + + "three four ", + TestFileType.TEXT); + executeAction("EditorCopy"); + executeAction("EditorCloneCaretBelow"); + executeAction("EditorLineEnd"); + executeAction("EditorPaste"); + checkResultByText("one twoone \n" + + "three fourone "); + } + + public void testCutAndPasteMultiline() throws Exception { + init("one two \n" + + "three four \n" + + "five six \n" + + "seven eight", + TestFileType.TEXT); + executeAction("EditorCut"); + executeAction("EditorLineEnd"); + executeAction("EditorPaste"); + checkResultByText("one fourtwo \n" + + "three \n" + + "five eightsix \n" + + "seven"); + } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretUndoRedoTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretUndoRedoTest.java new file mode 100644 index 000000000000..8bafc3e0b977 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretUndoRedoTest.java @@ -0,0 +1,118 @@ +/* + * 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.intellij.openapi.editor; + +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.command.impl.CurrentEditorProvider; +import com.intellij.openapi.command.impl.UndoManagerImpl; +import com.intellij.openapi.command.undo.UndoManager; +import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; +import com.intellij.openapi.editor.impl.AbstractEditorTest; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; +import com.intellij.testFramework.EditorTestUtil; +import com.intellij.testFramework.TestFileType; +import com.intellij.testFramework.fixtures.EditorScrollingFixture; +import org.jetbrains.annotations.NotNull; + +public class EditorMultiCaretUndoRedoTest extends AbstractEditorTest { + private CurrentEditorProvider mySavedCurrentEditorProvider; + + public void setUp() throws Exception { + super.setUp(); + EditorTestUtil.enableMultipleCarets(); + mySavedCurrentEditorProvider = getUndoManager().getEditorProvider(); + } + + public void tearDown() throws Exception { + getUndoManager().setEditorProvider(mySavedCurrentEditorProvider); + EditorTestUtil.disableMultipleCarets(); + super.tearDown(); + } + + @Override + // disabling execution of tests in command + protected void runTest() throws Throwable { + new WriteAction() { + @Override + protected void run(@NotNull Result result) throws Throwable { + doRunTest(); + } + }.execute(); + } + + public void testUndoRedo() throws Exception { + init("some text\n" + + "some other text\n" + + "another line", + TestFileType.TEXT); + setupEditorProvider(); + type('A'); + executeAction("EditorDelete"); + mouse().clickAt(0, 1); + undo(); + checkResult("someAtextAsome AAAline"); + undo(); + checkResult("someA textA\n" + + "some A A\n" + + "A line"); + undo(); + checkResult("some text\n" + + "some other text\n" + + "another line"); + redo(); + checkResult("someA textA\n" + + "some A A\n" + + "A line"); + } + + private void checkResult(final String text) { + CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { + @Override + public void run() { + checkResultByText(text); + } + }); + } + + private static void undo() { + getUndoManager().undo(getTextEditor()); + } + + private static void redo() { + getUndoManager().redo(getTextEditor()); + } + + private static UndoManagerImpl getUndoManager() { + return (UndoManagerImpl) UndoManager.getInstance(ourProject); + } + + private static TextEditor getTextEditor() { + return TextEditorProvider.getInstance().getTextEditor(myEditor); + } + + private static void setupEditorProvider() { + getUndoManager().setEditorProvider(new CurrentEditorProvider() { + @Override + public FileEditor getCurrentEditor() { + return getTextEditor(); + } + }); + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/VirtualFilePointerTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/VirtualFilePointerTest.java index 9b39ba18e29a..0471db8f1dc4 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/VirtualFilePointerTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/VirtualFilePointerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -524,27 +524,27 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { JobLauncher.getInstance().submitToJobThread(Job.DEFAULT_PRIORITY, new Runnable() { @Override public void run() { - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - VirtualFile file = pointer.getFile(); - if (file != null && !file.isValid()) { - throw new IncorrectOperationException("I've caught it. I am that good"); - } + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + VirtualFile file = pointer.getFile(); + if (file != null && !file.isValid()) { + throw new IncorrectOperationException("I've caught it. I am that good"); } - }); + } + }); } }, new Consumer() { - @Override - public void consume(Future future) { - try { - future.get(); - } - catch (Exception e) { - throw new RuntimeException(e); - } - } - }); + @Override + public void consume(Future future) { + try { + future.get(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); } } diff --git a/platform/remote-servers/api/src/com/intellij/remoteServer/ServerType.java b/platform/remote-servers/api/src/com/intellij/remoteServer/ServerType.java index 53f608c72b6e..6a50170b3716 100644 --- a/platform/remote-servers/api/src/com/intellij/remoteServer/ServerType.java +++ b/platform/remote-servers/api/src/com/intellij/remoteServer/ServerType.java @@ -3,6 +3,7 @@ package com.intellij.remoteServer; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.options.UnnamedConfigurable; import com.intellij.openapi.project.Project; +import com.intellij.remoteServer.configuration.RemoteServer; import com.intellij.remoteServer.configuration.ServerConfiguration; import com.intellij.remoteServer.configuration.deployment.DeploymentConfigurator; import com.intellij.remoteServer.runtime.ServerConnector; @@ -46,6 +47,11 @@ public abstract class ServerType { @NotNull public abstract ServerConnector createConnector(@NotNull C configuration, @NotNull ServerTaskExecutor asyncTasksExecutor); + @NotNull + public ServerConnector createConnector(@NotNull RemoteServer server, @NotNull ServerTaskExecutor asyncTasksExecutor) { + return createConnector(server.getConfiguration(), asyncTasksExecutor); + } + /** * @return a non-null instance of {@link DebugConnector} if the server supports deployment in debug mode */ diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ServerConnectionManagerImpl.java b/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ServerConnectionManagerImpl.java index d480270ce83f..b1a99655b9cf 100644 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ServerConnectionManagerImpl.java +++ b/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ServerConnectionManagerImpl.java @@ -27,7 +27,7 @@ public class ServerConnectionManagerImpl extends ServerConnectionManager { ServerConnection connection = myConnections.get(server); if (connection == null) { ServerTaskExecutorImpl executor = new ServerTaskExecutorImpl(); - connection = new ServerConnectionImpl(server, server.getType().createConnector(server.getConfiguration(), executor), this); + connection = new ServerConnectionImpl(server, server.getType().createConnector(server, executor), this); myConnections.put(server, connection); myEventDispatcher.fireConnectionCreated(connection); } diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyDialog.java b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyDialog.java index 666516e8fe36..38c5a131f5d4 100644 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyDialog.java +++ b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyDialog.java @@ -18,22 +18,22 @@ package com.intellij.remoteServer.util.ssh; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.ValidationInfo; -import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.io.File; /** * @author michael.golubev */ -public abstract class PublicSshKeyDialog extends DialogWrapper { +public class PublicSshKeyDialog extends DialogWrapper { - private PublicSshKeyPanel myPanel; + private PublicSshKeyFilePanel myPanel; - public PublicSshKeyDialog(@Nullable Project project, boolean textNotFile) { + public PublicSshKeyDialog(@Nullable Project project) { super(project); setTitle("Upload Public SSH Key"); - myPanel = textNotFile ? new PublicSshKeyTextPanel() : new PublicSshKeyFilePanel(); + myPanel = new PublicSshKeyFilePanel(); init(); } @@ -43,21 +43,13 @@ public abstract class PublicSshKeyDialog extends DialogWrapper { return myPanel.getMainPanel(); } - private String getSshKey() { - return myPanel.getSshKey(); + public File getSshKey() { + return new File(myPanel.getSshKey()); } @Nullable @Override protected ValidationInfo doValidate() { - return StringUtil.isEmpty(getSshKey()) ? new ValidationInfo("Specify public SSH key") : null; + return getSshKey().isFile() ? null : new ValidationInfo("Public SSH key file does not exist"); } - - @Override - protected void doOKAction() { - super.doOKAction(); - uploadSshKey(myPanel.getSshKey()); - } - - protected abstract void uploadSshKey(String sskKey); } diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyFilePanel.java b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyFilePanel.java index 4040fa0baed2..c16ff33dd991 100644 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyFilePanel.java +++ b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyFilePanel.java @@ -25,7 +25,7 @@ import javax.swing.*; /** * @author michael.golubev */ -public class PublicSshKeyFilePanel implements PublicSshKeyPanel { +public class PublicSshKeyFilePanel { private TextFieldWithBrowseButton myPublicSshKeyFileField; private HyperlinkLabel myPublicSshKeyTooltipHyperlink; @@ -36,7 +36,6 @@ public class PublicSshKeyFilePanel implements PublicSshKeyPanel { FileChooserDescriptorFactory.createSingleLocalFileDescriptor()); } - @Override public String getSshKey() { return myPublicSshKeyFileField.getText(); } @@ -47,7 +46,6 @@ public class PublicSshKeyFilePanel implements PublicSshKeyPanel { "Specify the location of your public SSH key file (.pub). You won’t be able to use the cloud unless you register you public key there. However, if you’ve already done that, you don’t have to specify the key now."); } - @Override public JComponent getMainPanel() { return myMainPanel; } diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyTextPanel.form b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyTextPanel.form deleted file mode 100644 index 35d635f22aee..000000000000 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyTextPanel.form +++ /dev/null @@ -1,43 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyTextPanel.java b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyTextPanel.java deleted file mode 100644 index 963c26e469cc..000000000000 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/PublicSshKeyTextPanel.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.intellij.remoteServer.util.ssh; - -import com.intellij.remoteServer.util.TooltipUtil; -import com.intellij.ui.HyperlinkLabel; - -import javax.swing.*; - -/** - * @author michael.golubev - */ -public class PublicSshKeyTextPanel implements PublicSshKeyPanel { - - private HyperlinkLabel myPublicSshKeyTooltipHyperlink; - private JTextField myPublicSshKeyTextField; - private JPanel myMainPanel; - - private void createUIComponents() { - myPublicSshKeyTooltipHyperlink = - TooltipUtil.createTooltip( - "Copy your public SSH key here. You won’t be able to use the cloud unless you register you public key there. However, if you’ve already done that, you don’t have to specify the key now."); - } - - @Override - public String getSshKey() { - return myPublicSshKeyTextField.getText(); - } - - @Override - public JComponent getMainPanel() { - return myMainPanel; - } -} diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/SshKeyAwareServerRuntime.java b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/SshKeyAwareServerRuntime.java index 7c92cf08890b..9c6008f53c71 100644 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/SshKeyAwareServerRuntime.java +++ b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/SshKeyAwareServerRuntime.java @@ -15,18 +15,20 @@ */ package com.intellij.remoteServer.util.ssh; -import com.intellij.remoteServer.configuration.ServerConfiguration; +import com.intellij.remoteServer.configuration.RemoteServer; import com.intellij.remoteServer.util.CloudServerRuntimeInstance; import com.intellij.remoteServer.util.ServerRuntimeException; +import java.io.File; + /** * @author michael.golubev */ public interface SshKeyAwareServerRuntime { - void addSshKey(String sshKey) throws ServerRuntimeException; + void addSshKey(File sshKey) throws ServerRuntimeException; CloudServerRuntimeInstance asCloudServerRuntime(); - ServerConfiguration getConfiguration(); + RemoteServer getServer(); } diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/SshKeyChecker.java b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/SshKeyChecker.java index b680a2c9c0d3..9eb3ea3e7fd8 100644 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/SshKeyChecker.java +++ b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/ssh/SshKeyChecker.java @@ -20,7 +20,6 @@ import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; -import com.intellij.remoteServer.configuration.ServerConfiguration; import com.intellij.remoteServer.runtime.ServerConnection; import com.intellij.remoteServer.runtime.ServerConnectionManager; import com.intellij.remoteServer.runtime.deployment.DeploymentLogManager; @@ -30,21 +29,15 @@ import com.intellij.remoteServer.runtime.ui.RemoteServersView; import com.intellij.remoteServer.util.*; import com.intellij.util.ParameterizedRunnable; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.swing.event.HyperlinkEvent; +import java.io.File; /** * @author michael.golubev */ public class SshKeyChecker { - private final boolean myTextNotFile; - - public SshKeyChecker(boolean textNotFile) { - myTextNotFile = textNotFile; - } - private static boolean isSshKeyErrorMessage(String errorMessage) { return errorMessage.contains("Could not read from remote repository") || errorMessage.contains("The remote end hung up unexpectedly"); } @@ -70,17 +63,6 @@ public class SshKeyChecker { } } - @Nullable - private static ServerConnection findServerConnection(SshKeyAwareServerRuntime serverRuntime) { - ServerConfiguration configuration = serverRuntime.getConfiguration(); - for (ServerConnection connection : ServerConnectionManager.getInstance().getConnections()) { - if (connection.getServer().getConfiguration() == configuration) { - return connection; - } - } - return null; - } - private class ServerHandler extends HandlerBase { private final CloudNotifier myNotifier; @@ -114,7 +96,7 @@ public class SshKeyChecker { } @Override - protected void uploadKey(final String sskKey) { + protected void uploadKey(final File sskKey) { new CloudConnectionTask(myProject, "Uploading SSH key", myConnectionTask.getServer()) { @Override @@ -178,7 +160,7 @@ public class SshKeyChecker { } @Override - protected void uploadKey(final String sskKey) { + protected void uploadKey(final File sskKey) { new CloudRuntimeTask(getProject(), "Uploading SSH key") { @Override @@ -212,10 +194,7 @@ public class SshKeyChecker { } private void redeploy() { - final ServerConnection connection = findServerConnection(myServerRuntime); - if (connection == null) { - return; - } + final ServerConnection connection = ServerConnectionManager.getInstance().getOrCreateConnection(myServerRuntime.getServer()); final RemoteServersView view = RemoteServersView.getInstance(myDeploymentTask.getProject()); view.showServerConnection(connection); @@ -231,19 +210,16 @@ public class SshKeyChecker { } } - private abstract class HandlerBase { + private static abstract class HandlerBase { protected void chooseKey() { - new PublicSshKeyDialog(getProject(), myTextNotFile) { - - @Override - protected void uploadSshKey(String sskKey) { - uploadKey(sskKey); - } - }.show(); + PublicSshKeyDialog dialog = new PublicSshKeyDialog(getProject()); + if (dialog.showAndGet()) { + uploadKey(dialog.getSshKey()); + } } - protected abstract void uploadKey(String sskKey); + protected abstract void uploadKey(File sskKeyFile); protected abstract Project getProject(); } diff --git a/platform/testFramework/src/com/intellij/testFramework/EditorTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/EditorTestUtil.java index a80ab7c1fbd3..b80b2350b8f5 100644 --- a/platform/testFramework/src/com/intellij/testFramework/EditorTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/EditorTestUtil.java @@ -17,9 +17,7 @@ package com.intellij.testFramework; import com.intellij.ide.DataManager; import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; import com.intellij.openapi.editor.actionSystem.EditorActionManager; import com.intellij.openapi.editor.actionSystem.TypedAction; @@ -39,6 +37,9 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + /** * User: Maxim.Mossienko * Date: 15.03.2010 @@ -284,6 +285,35 @@ public class EditorTestUtil { return result; } + public static void verifyCaretAndSelectionState(Editor editor, CaretsState caretState) { + CaretModel caretModel = editor.getCaretModel(); + List allCarets = new ArrayList(caretModel.getAllCarets()); + assertEquals("Unexpected number of carets", caretState.carets.size(), allCarets.size()); + for (int i = 0; i < caretState.carets.size(); i++) { + String caretDescription = caretState.carets.size() == 1 ? "" : "caret " + (i + 1) + "/" + caretState.carets.size() + " "; + com.intellij.openapi.editor.Caret currentCaret = allCarets.get(i); + LogicalPosition actualCaretPosition = currentCaret.getLogicalPosition(); + LogicalPosition actualSelectionStart = editor.offsetToLogicalPosition(currentCaret.getSelectionStart()); + LogicalPosition actualSelectionEnd = editor.offsetToLogicalPosition(currentCaret.getSelectionEnd()); + EditorTestUtil.Caret expected = caretState.carets.get(i); + if (expected.offset != null) { + LogicalPosition expectedCaretPosition = editor.offsetToLogicalPosition(expected.offset); + assertEquals(caretDescription + "unexpected caret position", expectedCaretPosition, actualCaretPosition); + } + if (expected.selection != null) { + LogicalPosition expectedSelectionStart = editor.offsetToLogicalPosition(expected.selection.getStartOffset()); + LogicalPosition expectedSelectionEnd = editor.offsetToLogicalPosition(expected.selection.getEndOffset()); + + assertEquals(caretDescription + "unexpected selection start", expectedSelectionStart, actualSelectionStart); + assertEquals(caretDescription + "unexpected selection end", expectedSelectionEnd, actualSelectionEnd); + } + else { + assertFalse(caretDescription + "should has no selection, but was: (" + actualSelectionStart + ", " + actualSelectionEnd + ")", + currentCaret.hasSelection()); + } + } + } + public static void enableMultipleCarets() { Registry.get("editor.allow.multiple.carets").setValue(true); } diff --git a/platform/util/src/com/intellij/openapi/diff/impl/ComparisonPolicy.java b/platform/util/src/com/intellij/openapi/diff/impl/ComparisonPolicy.java index e84e98cb0b5c..0767413e3f5b 100644 --- a/platform/util/src/com/intellij/openapi/diff/impl/ComparisonPolicy.java +++ b/platform/util/src/com/intellij/openapi/diff/impl/ComparisonPolicy.java @@ -201,18 +201,13 @@ public abstract class ComparisonPolicy { } private Object getWrapper(String line) { - line = line.trim(); - char[] chars = new char[line.length()]; - line.getChars(0, line.length(), chars, 0); - char[] result = new char[chars.length]; - int resultLength = 0; - for (int i = 0; i < chars.length; i++) { - char aChar = chars[i]; - if (Character.isWhitespace(aChar)) continue; - result[resultLength] = aChar; - resultLength++; + StringBuilder builder = new StringBuilder(line.length()); + for (int i = 0; i < line.length(); i++) { + char aChar = line.charAt(i); + if (StringUtil.isWhiteSpace(aChar)) continue; + builder.append(aChar); } - return new String(result, 0, resultLength); + return builder.toString(); } @Override diff --git a/platform/util/src/com/intellij/openapi/diff/impl/DiffFragmentBuilder.java b/platform/util/src/com/intellij/openapi/diff/impl/DiffFragmentBuilder.java index e333aca085b3..3b6a00b16fb9 100644 --- a/platform/util/src/com/intellij/openapi/diff/impl/DiffFragmentBuilder.java +++ b/platform/util/src/com/intellij/openapi/diff/impl/DiffFragmentBuilder.java @@ -156,7 +156,9 @@ public class DiffFragmentBuilder { } private static String concatenate(String[] strings, int start, int end) { - StringBuffer buffer = new StringBuffer(); + int len = 0; + for (int i = start - 1; i < end; i++) len += strings[i] == null ? 0 : strings[i].length(); + StringBuilder buffer = new StringBuilder(len); for (int i = start - 1; i < end; i++) buffer.append(strings[i]); return buffer.toString(); } diff --git a/platform/util/src/com/intellij/openapi/diff/impl/highlighting/Util.java b/platform/util/src/com/intellij/openapi/diff/impl/highlighting/Util.java index 0d6e1f762633..83cfa988c573 100644 --- a/platform/util/src/com/intellij/openapi/diff/impl/highlighting/Util.java +++ b/platform/util/src/com/intellij/openapi/diff/impl/highlighting/Util.java @@ -232,23 +232,33 @@ public class Util { } public static DiffFragment concatenate(DiffFragment[] line, int from, int to) { - StringBuffer buffer1 = new StringBuffer(); - StringBuffer buffer2 = new StringBuffer(); + String[] data1 = new String[to - from]; + String[] data2 = new String[to - from]; + + int len1 = 0; + int len2 = 0; boolean isEqual = true; - for (int j = from; j < to; j++) { - DiffFragment fragment = line[j]; - isEqual &= fragment.isEqual(); - String text1 = fragment.getText1(); - String text2 = fragment.getText2(); - if (text1 != null) buffer1.append(text1); - if (text2 != null) buffer2.append(text2); + for (int i = 0; i < to - from; i++) { + isEqual &= line[i + from].isEqual(); + data1[i] = line[i + from].getText1(); + data2[i] = line[i + from].getText2(); + len1 += data1[i] == null ? 0 : data1[i].length(); + len2 += data2[i] == null ? 0 : data2[i].length(); } + + StringBuilder buffer1 = new StringBuilder(len1); + StringBuilder buffer2 = new StringBuilder(len2); + for (int i = 0; i < to - from; i++) { + if (data1[i] != null) buffer1.append(data1[i]); + if (data2[i] != null) buffer2.append(data2[i]); + } + String text1 = notEmptyContent(buffer1); String text2 = notEmptyContent(buffer2); return isEqual ? DiffFragment.unchanged(text1, text2) : new DiffFragment(text1, text2); } - private static String notEmptyContent(StringBuffer buffer) { + private static String notEmptyContent(StringBuilder buffer) { return buffer.length() > 0 ? buffer.toString() : null; } diff --git a/platform/util/src/com/intellij/openapi/diff/impl/processing/ByWord.java b/platform/util/src/com/intellij/openapi/diff/impl/processing/ByWord.java index 7a7515af44b5..21740dc83a67 100644 --- a/platform/util/src/com/intellij/openapi/diff/impl/processing/ByWord.java +++ b/platform/util/src/com/intellij/openapi/diff/impl/processing/ByWord.java @@ -199,8 +199,8 @@ public class ByWord implements DiffPolicy{ int lastIndex = myFragments.size() - 1; DiffFragment prevFragment = myFragments.get(lastIndex); if (prevFragment.isEqual()) { - prevFragment.appendText1(fragment.getText1()); - prevFragment.appendText2(fragment.getText2()); + prevFragment.appendText1(text1); + prevFragment.appendText2(text2); return; } } diff --git a/platform/util/src/com/intellij/openapi/diff/impl/processing/DummyDiffFragmentsProcessor.java b/platform/util/src/com/intellij/openapi/diff/impl/processing/DummyDiffFragmentsProcessor.java new file mode 100644 index 000000000000..8da7510145b8 --- /dev/null +++ b/platform/util/src/com/intellij/openapi/diff/impl/processing/DummyDiffFragmentsProcessor.java @@ -0,0 +1,56 @@ +/* + * 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.intellij.openapi.diff.impl.processing; + +import com.intellij.openapi.diff.impl.fragments.LineFragment; +import com.intellij.openapi.diff.impl.util.TextDiffTypeEnum; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; + +import java.util.ArrayList; + +public class DummyDiffFragmentsProcessor { + public ArrayList process(String text1, String text2) { + ArrayList lineFragments = new ArrayList(); + + if (text1.isEmpty() && text2.isEmpty()) { + return lineFragments; + } + + TextDiffTypeEnum type; + if (text1.isEmpty()) { + type = TextDiffTypeEnum.INSERT; + } + else if (text2.isEmpty()) { + type = TextDiffTypeEnum.DELETED; + } + else { + type = TextDiffTypeEnum.CHANGED; + } + lineFragments.add(new LineFragment(0, countLines(text1), 0, countLines(text2), type, new TextRange(0, text1.length()), + new TextRange(0, text2.length()))); + + return lineFragments; + } + + private static int countLines(String text) { + if (text == null || text.isEmpty()) return 0; + int count = StringUtil.countNewLines(text); + if (text.charAt(text.length() - 1) != '\n') count++; + return count; + } + +} diff --git a/platform/util/src/com/intellij/util/ResourceUtil.java b/platform/util/src/com/intellij/util/ResourceUtil.java index 469e79d8b7e9..a2a857b8687c 100644 --- a/platform/util/src/com/intellij/util/ResourceUtil.java +++ b/platform/util/src/com/intellij/util/ResourceUtil.java @@ -15,6 +15,7 @@ */ package com.intellij.util; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.io.URLUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -38,11 +39,15 @@ public class ResourceUtil { } public static URL getResource(@NotNull Class loaderClass, @NonNls @NotNull String basePath, @NonNls @NotNull String fileName) { - if (basePath.endsWith("/")) basePath = basePath.substring(0, basePath.length() - 1); + return getResource(loaderClass.getClassLoader(), basePath, fileName); + } - final List bundles = calculateBundleNames(basePath, Locale.getDefault()); + public static URL getResource(@NotNull ClassLoader loader, @NonNls @NotNull String basePath, @NonNls @NotNull String fileName) { + String fixedPath = StringUtil.trimStart(StringUtil.trimEnd(basePath, "/"), "/"); + + List bundles = calculateBundleNames(fixedPath, Locale.getDefault()); for (String bundle : bundles) { - URL url = loaderClass.getResource(bundle + "/" + fileName); + URL url = loader.getResource(bundle + "/" + fileName); if (url == null) continue; try { @@ -55,7 +60,7 @@ public class ResourceUtil { return url; } - return loaderClass.getResource(basePath + "/" + fileName); + return loader.getResource(fixedPath + "/" + fileName); } /** diff --git a/platform/util/src/com/intellij/util/containers/ContainerUtil.java b/platform/util/src/com/intellij/util/containers/ContainerUtil.java index cb5bb0ad8b16..a5d5bd886531 100644 --- a/platform/util/src/com/intellij/util/containers/ContainerUtil.java +++ b/platform/util/src/com/intellij/util/containers/ContainerUtil.java @@ -1970,15 +1970,20 @@ public class ContainerUtil extends ContainerUtilRt { * - slower modification in highly contented case (which is the kind of situation you shouldn't use COWAL anyway) */ @NotNull - public static ConcurrentList createLockFreeCopyOnWriteList() { - return new LockFreeCopyOnWriteArrayList(); + public static List createLockFreeCopyOnWriteList() { + return createConcurrentList(); } @NotNull - public static ConcurrentList createLockFreeCopyOnWriteList(@NotNull Collection c) { + public static List createLockFreeCopyOnWriteList(@NotNull Collection c) { return new LockFreeCopyOnWriteArrayList(c); } + @NotNull + public static ConcurrentList createConcurrentList() { + return new LockFreeCopyOnWriteArrayList(); + } + public static void addIfNotNull(@Nullable T element, @NotNull Collection result) { ContainerUtilRt.addIfNotNull(element, result); } diff --git a/platform/util/src/com/intellij/util/containers/OrderedSet.java b/platform/util/src/com/intellij/util/containers/OrderedSet.java index 25682ab868cd..9900eb92e800 100644 --- a/platform/util/src/com/intellij/util/containers/OrderedSet.java +++ b/platform/util/src/com/intellij/util/containers/OrderedSet.java @@ -15,13 +15,11 @@ */ package com.intellij.util.containers; +import com.intellij.util.IncorrectOperationException; import gnu.trove.TObjectHashingStrategy; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Collection; -import java.util.RandomAccess; -import java.util.Set; +import java.util.*; // have to extend ArrayList because otherwise the spliterator() methods declared in Set and List are in conflict public class OrderedSet extends ArrayList implements Set, RandomAccess { @@ -40,6 +38,33 @@ public class OrderedSet extends ArrayList implements Set, RandomAccess myHashSet = new OpenTHashSet(capacity, hashingStrategy); } + @Override + public boolean removeAll(@NotNull Collection c) { + boolean removed = false; + for (Object o : c) { + removed |= remove(o); + } + return removed; + } + + @Override + public boolean retainAll(@NotNull Collection c) { + boolean removed = false; + for (int i = size() - 1; i >= 0; i--) { + Object o = get(i); + if (!c.contains(o)) { + removed |= remove(o); + } + } + return removed; + } + + @NotNull + @Override + public List subList(int fromIndex, int toIndex) { + throw new IncorrectOperationException(); + } + @Override public boolean contains(Object o) { return myHashSet.contains(o); diff --git a/platform/util/src/com/intellij/util/diff/Diff.java b/platform/util/src/com/intellij/util/diff/Diff.java index d5d96886fa58..d41175fb3a98 100644 --- a/platform/util/src/com/intellij/util/diff/Diff.java +++ b/platform/util/src/com/intellij/util/diff/Diff.java @@ -16,6 +16,7 @@ package com.intellij.util.diff; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.LineTokenizer; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Enumerator; @@ -24,6 +25,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.BitSet; /** * @author dyoma @@ -49,15 +51,41 @@ public class Diff { final int startShift = getStartShift(objects1, objects2); final int endCut = getEndCut(objects1, objects2, startShift); + ChangeBuilder builder = new ChangeBuilder(startShift); + int trimmedLength1 = objects1.length - startShift - endCut; + int trimmedLength2 = objects2.length - startShift - endCut; + if (trimmedLength1 == 0 || trimmedLength2 == 0) { + if (trimmedLength1 != 0 || trimmedLength2 != 0) { + builder.addChange(trimmedLength1, trimmedLength2); + } + return builder.getFirstChange(); + } + Enumerator enumerator = new Enumerator(objects1.length + objects2.length, ContainerUtil.canonicalStrategy()); int[] ints1 = enumerator.enumerate(objects1, startShift, endCut); int[] ints2 = enumerator.enumerate(objects2, startShift, endCut); - Reindexer reindexer = new Reindexer(); + Reindexer reindexer = new Reindexer(); // discard unique elements, that have no chance to be matched int[][] discarded = reindexer.discardUnique(ints1, ints2); - IntLCS intLCS = new IntLCS(discarded[0], discarded[1]); - intLCS.execute(); - ChangeBuilder builder = new ChangeBuilder(startShift); - reindexer.reindex(intLCS.getPaths(), builder); + + if (discarded[0].length == 0 && discarded[1].length == 0) { + // assert trimmedLength > 0 + builder.addChange(ints1.length, ints2.length); + return builder.getFirstChange(); + } + + BitSet[] changes; + if (Registry.is("diff.patience.alg")) { + PatienceIntLCS patienceIntLCS = new PatienceIntLCS(discarded[0], discarded[1]); + patienceIntLCS.execute(); + changes = patienceIntLCS.getChanges(); + } + else { + IntLCS intLCS = new IntLCS(discarded[0], discarded[1]); + intLCS.execute(); + changes = intLCS.getChanges(); + } + + reindexer.reindex(changes, builder); return builder.getFirstChange(); } diff --git a/platform/util/src/com/intellij/util/diff/IntLCS.java b/platform/util/src/com/intellij/util/diff/IntLCS.java index 1a6529483442..7fc7162f27ed 100644 --- a/platform/util/src/com/intellij/util/diff/IntLCS.java +++ b/platform/util/src/com/intellij/util/diff/IntLCS.java @@ -16,6 +16,7 @@ package com.intellij.util.diff; import java.util.Arrays; +import java.util.BitSet; /** * @author dyoma @@ -24,6 +25,9 @@ class IntLCS { private final int[] myFirst; private final int[] mySecond; + private final int myStart1; + private final int myStart2; + private final LinkedDiffPaths myPathsMatrix; private final int[] myPrevPathKey; private int[] myPrevEnds; @@ -31,11 +35,23 @@ class IntLCS { private final int myMaxX; private final int myMaxY; + private final BitSet myChanges1; + private final BitSet myChanges2; + public IntLCS(int[] first, int[] second) { + this(first, second, 0, first.length, 0, second.length, new BitSet(first.length), new BitSet(second.length)); + } + + public IntLCS(int[] first, int[] second, int start1, int count1, int start2, int count2, BitSet changes1, BitSet changes2) { myFirst = first; mySecond = second; - myMaxX = myFirst.length; - myMaxY = mySecond.length; + myStart1 = start1; + myStart2 = start2; + myMaxX = count1; + myMaxY = count2; + + myChanges1 = changes1; + myChanges2 = changes2; myPathsMatrix = new LinkedDiffPaths(myMaxX, myMaxY); myPrevPathKey = new int[myMaxX + myMaxY + 1]; @@ -82,7 +98,10 @@ class IntLCS { } } myCurrentEnds[k + myMaxY] = end; - if (k == myMaxX - myMaxY && end == myMaxX) return d; + if (k == myMaxX - myMaxY && end == myMaxX) { + myPathsMatrix.applyChanges(myStart1, myStart2, myChanges1, myChanges2); + return d; + } } int[] temps = myCurrentEnds; myCurrentEnds = myPrevEnds; @@ -91,8 +110,8 @@ class IntLCS { throw new RuntimeException(); } - public LinkedDiffPaths getPaths() { - return myPathsMatrix; + public BitSet[] getChanges() { + return new BitSet[]{myChanges1, myChanges2}; } private int findDiagonalEnd(int prevDiagonal, int prevEnd, boolean isVertical) { @@ -122,7 +141,7 @@ class IntLCS { private int skipEquals(int x, int y) { int skipped = 0; - while (x < myMaxX && y < myMaxY && myFirst[x] == mySecond[y]) { + while (x < myMaxX && y < myMaxY && myFirst[myStart1 + x] == mySecond[myStart2 + y]) { skipped += 1; x++; y++; diff --git a/platform/util/src/com/intellij/util/diff/LinkedDiffPaths.java b/platform/util/src/com/intellij/util/diff/LinkedDiffPaths.java index 21eea4824227..bc7582077940 100644 --- a/platform/util/src/com/intellij/util/diff/LinkedDiffPaths.java +++ b/platform/util/src/com/intellij/util/diff/LinkedDiffPaths.java @@ -15,6 +15,8 @@ */ package com.intellij.util.diff; +import java.util.BitSet; + /** * @author dyoma */ @@ -30,10 +32,38 @@ final class LinkedDiffPaths { private static final int DISTANCE_MASK = ~VERTICAL_DIRECTION_FLAG; public LinkedDiffPaths(int maxX, int maxY) { - myMaxX = maxX-1; - myMaxY = maxY-1; + myMaxX = maxX; + myMaxY = maxY; } + public void applyChanges(final int start1, final int start2, final BitSet changes1, final BitSet changes2) { + decodePath(new LCSBuilder() { + int x = myMaxX; + int y = myMaxY; + + @Override + public void addEqual(int length) { + x -= length; + y -= length; + } + + @Override + public void addChange(int first, int second) { + if (first > 0) { + changes1.set(start1 + x - first, start1 + x); + x -= first; + } + if (second > 0) { + changes2.set(start2 + y - second, start2 + y); + y -= second; + } + } + }); + } + + /** + * Path is decoded in reverse order (from the last change to the first) + */ public Builder decodePath(Builder builder) { Decoder decoder = new Decoder(getXSize(), getYSize(), builder); int index = myCornerIndex; @@ -47,11 +77,11 @@ final class LinkedDiffPaths { } public int getXSize() { - return myMaxX + 1; + return myMaxX; } public int getYSize() { - return myMaxY + 1; + return myMaxY; } public int encodeStep(int x, int y, int diagLength, boolean afterVertical, int prevIndex) throws FilesTooBigForDiffException { @@ -61,7 +91,7 @@ final class LinkedDiffPaths { myPrevSteps[position] = prevIndex; mySteps[position] = encodedPath; - if (x == myMaxX && y == myMaxY) myCornerIndex = position; + if (x == myMaxX - 1 && y == myMaxY - 1) myCornerIndex = position; return position; } diff --git a/platform/util/src/com/intellij/util/diff/PatienceIntLCS.java b/platform/util/src/com/intellij/util/diff/PatienceIntLCS.java new file mode 100644 index 000000000000..8f04dfa10257 --- /dev/null +++ b/platform/util/src/com/intellij/util/diff/PatienceIntLCS.java @@ -0,0 +1,150 @@ +/* + * 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.intellij.util.diff; + +import java.util.BitSet; + +public class PatienceIntLCS { + private final int[] myFirst; + private final int[] mySecond; + + private final int myStart1; + private final int myStart2; + private final int myCount1; + private final int myCount2; + + private final BitSet myChanges1; + private final BitSet myChanges2; + + public PatienceIntLCS(int[] first, int[] second) { + this(first, second, 0, first.length, 0, second.length, new BitSet(first.length), new BitSet(second.length)); + } + + public PatienceIntLCS(int[] first, int[] second, int start1, int count1, int start2, int count2, BitSet changes1, BitSet changes2) { + myFirst = first; + mySecond = second; + myStart1 = start1; + myStart2 = start2; + myCount1 = count1; + myCount2 = count2; + + myChanges1 = changes1; + myChanges2 = changes2; + } + + public void execute() throws FilesTooBigForDiffException { + if (myCount1 == 0 && myCount2 == 0) { + return; + } + + if (myCount1 == 0 || myCount2 == 0) { + addChange(myStart1, myCount1, myStart2, myCount2); + return; + } + + int startOffset = matchForward(0, 0); + int start1 = myStart1 + startOffset; + int start2 = myStart2 + startOffset; + + int endOffset = matchBackward(myStart1 + myCount1 - 1, myStart2 + myCount2 - 1, start1, start2); + int count1 = myCount1 - startOffset - endOffset; + int count2 = myCount2 - startOffset - endOffset; + + if (count1 == 0 || count2 == 0) { + addChange(start1, count1, start2, count2); + } + else { + UniqueLCS uniqueLCS = new UniqueLCS(myFirst, mySecond, start1, count1, start2, count2); + int[][] matching = uniqueLCS.execute(); + + if (matching == null) { + IntLCS intLCS = new IntLCS(myFirst, mySecond, start1, count1, start2, count2, myChanges1, myChanges2); + intLCS.execute(); + } + else { + int s1, s2, c1, c2; + int matched = matching[0].length; + assert matched > 0; + + PatienceIntLCS patienceDiff = + new PatienceIntLCS(myFirst, mySecond, start1, matching[0][0], start2, matching[1][0], myChanges1, myChanges2); + patienceDiff.execute(); + + for (int i = 1; i < matching[0].length; i++) { + s1 = matching[0][i - 1] + 1; + s2 = matching[1][i - 1] + 1; + + c1 = matching[0][i] - s1; + c2 = matching[1][i] - s2; + + if (c1 > 0 || c2 > 0) { + patienceDiff = new PatienceIntLCS(myFirst, mySecond, start1 + s1, c1, start2 + s2, c2, myChanges1, myChanges2); + patienceDiff.execute(); + } + } + + if (matching[0][matched - 1] == count1 - 1) { + s1 = count1 - 1; + c1 = 0; + } + else { + s1 = matching[0][matched - 1] + 1; + c1 = count1 - s1; + } + if (matching[1][matched - 1] == count2 - 1) { + s2 = count2 - 1; + c2 = 0; + } + else { + s2 = matching[1][matched - 1] + 1; + c2 = count2 - s2; + } + + patienceDiff = new PatienceIntLCS(myFirst, mySecond, start1 + s1, c1, start2 + s2, c2, myChanges1, myChanges2); + patienceDiff.execute(); + } + } + } + + private int matchForward(int offset1, int offset2) { + final int size = Math.min(myCount1 - offset1, myCount2 - offset2); + int idx = 0; + for (int i = 0; i < size; i++) { + if (!(myFirst[offset1 + i] == mySecond[offset2 + i])) break; + ++idx; + } + return idx; + } + + private int matchBackward(int offset1, int offset2, int processedOffset1, int processedOffset2) { + final int size = Math.min(offset1 - processedOffset1 - 1, offset2 - processedOffset2 - 1); + int idx = 0; + for (int i = 0; i < size; i++) { + if (!(myFirst[offset1 - i] == mySecond[offset2 - i])) break; + ++idx; + } + return idx; + } + + public void addChange(int start1, int count1, int start2, int count2) { + myChanges1.set(start1, start1 + count1); + myChanges2.set(start2, start2 + count2); + } + + public BitSet[] getChanges() { + return new BitSet[]{myChanges1, myChanges2}; + } +} diff --git a/platform/util/src/com/intellij/util/diff/Reindexer.java b/platform/util/src/com/intellij/util/diff/Reindexer.java index c390e6950847..e30849e5853b 100644 --- a/platform/util/src/com/intellij/util/diff/Reindexer.java +++ b/platform/util/src/com/intellij/util/diff/Reindexer.java @@ -18,6 +18,7 @@ package com.intellij.util.diff; import gnu.trove.TIntArrayList; import java.util.Arrays; +import java.util.BitSet; /** * @author dyoma @@ -25,6 +26,7 @@ import java.util.Arrays; class Reindexer { private final int[][] myOldIndecies = new int[2][]; private final int[] myOriginalLengths = new int[]{-1, -1}; + private final int[] myDiscardedLengths = new int[]{-1, -1}; public int[][] discardUnique(int[] ints1, int[] ints2) { int[] discarded1 = discard(ints2, ints1, 0); @@ -34,6 +36,8 @@ class Reindexer { void idInit(int length1, int length2) { myOriginalLengths[0] = length1; myOriginalLengths[1] = length2; + myDiscardedLengths[0] = length1; + myDiscardedLengths[1] = length2; for (int j = 0; j < 2; j++) { int originalLength = myOriginalLengths[j]; myOldIndecies[j] = new int[originalLength]; @@ -59,6 +63,7 @@ class Reindexer { } } myOldIndecies[arrayIndex] = oldIndecies.toNativeArray(); + myDiscardedLengths[arrayIndex] = discarded.size(); return discarded.toNativeArray(); } @@ -69,67 +74,77 @@ class Reindexer { return sorted1; } - public void reindex(LinkedDiffPaths paths, LCSBuilder builder) { - final boolean[] changes1 = new boolean[myOriginalLengths[0]]; - final boolean[] changes2 = new boolean[myOriginalLengths[1]]; - Arrays.fill(changes1, true); - Arrays.fill(changes2, true); - paths.decodePath(new LCSBuilder() { - private int x = myOldIndecies[0].length - 1; - private int y = myOldIndecies[1].length - 1; - private int originalX = myOriginalLengths[0] - 1; - private int originalY = myOriginalLengths[1] - 1; + public void reindex(BitSet[] discardedChanges, LCSBuilder builder) { + BitSet changes1 = new BitSet(myOriginalLengths[0]); + BitSet changes2 = new BitSet(myOriginalLengths[1]); - @Override - public void addChange(int first, int second) { - x -= first; - y -= second; - originalX = markChanged(changes1, originalX, myOldIndecies[0], x); - originalY = markChanged(changes2, originalY, myOldIndecies[1], y); - } - - @Override - public void addEqual(int length) { - for (int i = length; i > 0; i--) { - originalX = markChanged(changes1, originalX, myOldIndecies[0], x); - originalY = markChanged(changes2, originalY, myOldIndecies[1], y); - x--; - y--; - changes1[originalX] = false; - changes2[originalY] = false; - originalX--; - originalY--; - } - } - }); int x = 0; int y = 0; - while (x < changes1.length && y < changes2.length) { + while (x < myDiscardedLengths[0] || y < myDiscardedLengths[1]) { + if ((x < myDiscardedLengths[0] && y < myDiscardedLengths[1]) && !discardedChanges[0].get(x) && !discardedChanges[1].get(y)) { + x = increment(myOldIndecies[0], x, changes1, myOriginalLengths[0]); + y = increment(myOldIndecies[1], y, changes2, myOriginalLengths[1]); + continue; + } + if (discardedChanges[0].get(x)) { + changes1.set(getOriginal(myOldIndecies[0], x)); + x = increment(myOldIndecies[0], x, changes1, myOriginalLengths[0]); + continue; + } + if (discardedChanges[1].get(y)) { + changes2.set(getOriginal(myOldIndecies[1], y)); + y = increment(myOldIndecies[1], y, changes2, myOriginalLengths[1]); + continue; + } + } + if (myDiscardedLengths[0] == 0) { + changes1.set(0, myOriginalLengths[0]); + } + else { + changes1.set(0, myOldIndecies[0][0]); + } + if (myDiscardedLengths[1] == 0) { + changes2.set(0, myOriginalLengths[1]); + } + else { + changes2.set(0, myOldIndecies[1][0]); + } + + x = 0; + y = 0; + while (x < myOriginalLengths[0] && y < myOriginalLengths[1]) { int startX = x; - while (x < changes1.length && y < changes2.length && !changes1[x] && !changes2[y]) { + while (x < myOriginalLengths[0] && y < myOriginalLengths[1] && !changes1.get(x) && !changes2.get(y)) { x++; y++; } if (x> startX) builder.addEqual(x - startX); int dx = 0; int dy = 0; - while (x < changes1.length && changes1[x]) { + while (x < myOriginalLengths[0] && changes1.get(x)) { dx++; x++; } - while (y < changes2.length && changes2[y]) { + while (y < myOriginalLengths[1] && changes2.get(y)) { dy++; y++; } if (dx != 0 || dy != 0) builder.addChange(dx, dy); } - if (x != changes1.length || y != changes2.length) - builder.addChange(changes1.length - x, changes2.length - y); + if (x != myOriginalLengths[0] || y != myOriginalLengths[1]) builder.addChange(myOriginalLengths[0] - x, myOriginalLengths[1] - y); } - private int markChanged(final boolean[] changes, int from, int[] oldIndecies, int newTo) { - int oldTo = newTo != -1 ? oldIndecies[newTo] : -1; - for (int i = from; i > oldTo; i--) changes[i] = true; - return oldTo; + private int getOriginal(int[] indexes, int i) { + return indexes[i]; + } + + private int increment(int[] indexes, int i, BitSet set, int length) { + if (i + 1 < indexes.length) { + set.set(indexes[i] + 1, indexes[i + 1]); + } + else { + set.set(indexes[i] + 1, length); + } + return i + 1; } } diff --git a/platform/util/src/com/intellij/util/diff/UniqueLCS.java b/platform/util/src/com/intellij/util/diff/UniqueLCS.java new file mode 100644 index 000000000000..636a0c824637 --- /dev/null +++ b/platform/util/src/com/intellij/util/diff/UniqueLCS.java @@ -0,0 +1,134 @@ +/* + * 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.intellij.util.diff; + +import gnu.trove.TIntIntHashMap; + +public class UniqueLCS { + private final int[] myFirst; + private final int[] mySecond; + + private final int myStart1; + private final int myStart2; + private final int myCount1; + private final int myCount2; + + public UniqueLCS(int[] first, int[] second) { + this(first, second, 0, first.length, 0, second.length); + } + + public UniqueLCS(int[] first, int[] second, int start1, int count1, int start2, int count2) { + myFirst = first; + mySecond = second; + myStart1 = start1; + myStart2 = start2; + myCount1 = count1; + myCount2 = count2; + } + + public int[][] execute() { + // map: key -> (offset1 + 1) + // match: offset1 -> (offset2 + 1) + TIntIntHashMap map = new TIntIntHashMap(myCount1 + myCount2); + int[] match = new int[myCount1]; + + for (int i = 0; i < myCount1; i++) { + int index = myStart1 + i; + int val = map.get(myFirst[index]); + + if (val == -1) continue; + if (val == 0) { + map.put(myFirst[index], i + 1); + } + else { + map.put(myFirst[index], -1); + } + } + + int count = 0; + for (int i = 0; i < myCount2; i++) { + int index = myStart2 + i; + int val = map.get(mySecond[index]); + + if (val == 0 || val == -1) continue; + if (match[val - 1] == 0) { + match[val - 1] = i + 1; + count++; + } + else { + match[val - 1] = 0; + map.put(mySecond[index], -1); + count--; + } + } + + if (count == 0) { + return null; + } + + // Largest increasing subsequence on unique elements + int[] sequence = new int[count]; + int[] lastElement = new int[count]; + int[] predecessor = new int[myCount1]; + + int length = 0; + for (int i = 0; i < myCount1; i++) { + if (match[i] == 0) continue; + + int j = binarySearch(sequence, match[i], length); + if (j == length || match[i] < sequence[j]) { + sequence[j] = match[i]; + lastElement[j] = i; + predecessor[i] = j > 0 ? lastElement[j - 1] : -1; + if (j == length) { + length++; + } + } + } + + int[][] ret = new int[][]{new int[length], new int[length]}; + + int i = length - 1; + int curr = lastElement[length - 1]; + while (curr != -1) { + ret[0][i] = curr; + ret[1][i] = match[curr] - 1; + i--; + curr = predecessor[curr]; + } + + return ret; + } + + // find max i: a[i] < val + // return i + 1 + // assert a[i] != val + private static int binarySearch(int[] sequence, int val, int length) { + int left = -1; + int right = length; + + while (right - left > 1) { + int middle = (left + right) / 2; + if (sequence[middle] > val) { + right = middle; + } + else { + left = middle; + } + } + return left + 1; + } +} diff --git a/platform/util/testSrc/com/intellij/util/diff/IntLCSNewTest.java b/platform/util/testSrc/com/intellij/util/diff/IntLCSNewTest.java new file mode 100644 index 000000000000..31fd1ad0cbcd --- /dev/null +++ b/platform/util/testSrc/com/intellij/util/diff/IntLCSNewTest.java @@ -0,0 +1,87 @@ +/* + * 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.intellij.util.diff; + +import junit.framework.TestCase; + +import java.util.BitSet; + +public class IntLCSNewTest extends TestCase { + public void testEqual() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2, 3}, new int[]{1, 2, 3}); + checkChange(change, new int[]{0, 0, 0}, new int[]{0, 0, 0}); + } + + public void testOneAtBegging() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2}, new int[]{1, 3}); + checkChange(change, new int[]{0, 1}, new int[]{0, 1}); + } + + public void testOneAntEnd() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 3}, new int[]{2, 3}); + checkChange(change, new int[]{1, 0}, new int[]{1, 0}); + } + + public void testOneOverAtEnd() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2}, new int[]{1, 2, 3}); + checkChange(change, new int[]{0, 0}, new int[]{0, 0, 1}); + } + + public void testOneOverAtBegging() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2, 3}, new int[]{2, 3}); + checkChange(change, new int[]{1, 0, 0}, new int[]{0, 0}); + } + + public void testSingleMiddle() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2, 3}, new int[]{4, 2, 5}); + checkChange(change, new int[]{1, 0, 1}, new int[]{1, 0, 1}); + } + + public void testAbsolutelyDifferent() throws FilesTooBigForDiffException { + BitSet[] change1 = buildChange(new int[]{1, 2}, new int[]{3, 4}); + checkChange(change1, new int[]{1, 1}, new int[]{1, 1}); + + BitSet[] change2 = buildChange(new int[]{1, 2, 3}, new int[]{4, 5, 6}); + checkChange(change2, new int[]{1, 1, 1}, new int[]{1, 1, 1}); + } + + public void testSingleUniqueMoved() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 1, 2, 2, 10}, new int[]{10, 1, 1, 2, 2}); + checkChange(change, new int[]{0, 0, 0, 0, 1}, new int[]{1, 0, 0, 0, 0}); + } + + public void testSingleFunctionMoved() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 7, 9, 10, 2, 11, 12, 5, 13, 7, 8, 7}, + new int[]{10, 2, 11, 12, 5, 13, 7, 8, 7, 9, 1, 2, 3, 4, 5, 6, 7, 8, 7}); + checkChange(change, new int[]{1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1}, + new int[]{1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1}); + } + + private static BitSet[] buildChange(int[] first, int[] second) throws FilesTooBigForDiffException { + IntLCS intLCS = new IntLCS(first, second); + intLCS.execute(); + return intLCS.getChanges(); + } + + private static void checkChange(BitSet[] change, int[] expected1, int[] expected2) { + for (int i = 0; i < expected1.length; i++) { + assertEquals(change[0].get(i), expected1[i] == 1); + } + for (int i = 0; i < expected2.length; i++) { + assertEquals(change[1].get(i), expected2[i] == 1); + } + } +} diff --git a/platform/util/testSrc/com/intellij/util/diff/IntLCSTest.java b/platform/util/testSrc/com/intellij/util/diff/IntLCSTest.java index c2a350dab1c2..87d1fad58435 100644 --- a/platform/util/testSrc/com/intellij/util/diff/IntLCSTest.java +++ b/platform/util/testSrc/com/intellij/util/diff/IntLCSTest.java @@ -68,7 +68,7 @@ public class IntLCSTest extends TestCase { Reindexer reindexer = new Reindexer(); reindexer.idInit(first.length, second.length); Diff.ChangeBuilder builder = new Diff.ChangeBuilder(0); - reindexer.reindex(intLCS.getPaths(), builder); + reindexer.reindex(intLCS.getChanges(), builder); return builder.getFirstChange(); } diff --git a/platform/util/testSrc/com/intellij/util/diff/LinkedDiffPathsTest.java b/platform/util/testSrc/com/intellij/util/diff/LinkedDiffPathsTest.java index befb38a60b8d..0f9300ab3d8c 100644 --- a/platform/util/testSrc/com/intellij/util/diff/LinkedDiffPathsTest.java +++ b/platform/util/testSrc/com/intellij/util/diff/LinkedDiffPathsTest.java @@ -18,6 +18,8 @@ package com.intellij.util.diff; import junit.framework.TestCase; +import java.util.BitSet; + /** * @author dyoma */ @@ -67,10 +69,12 @@ public class LinkedDiffPathsTest extends TestCase { } private Diff.Change decode(LinkedDiffPaths paths) { + BitSet[] changes = new BitSet[]{new BitSet(), new BitSet()}; + paths.applyChanges(0, 0, changes[0], changes[1]); Reindexer reindexer = new Reindexer(); reindexer.idInit(paths.getXSize(), paths.getYSize()); Diff.ChangeBuilder builder = new Diff.ChangeBuilder(0); - reindexer.reindex(paths, builder); + reindexer.reindex(changes, builder); return builder.getFirstChange(); } } diff --git a/platform/util/testSrc/com/intellij/util/diff/PatienceIntLCSTest.java b/platform/util/testSrc/com/intellij/util/diff/PatienceIntLCSTest.java new file mode 100644 index 000000000000..eb9c1dc2905e --- /dev/null +++ b/platform/util/testSrc/com/intellij/util/diff/PatienceIntLCSTest.java @@ -0,0 +1,108 @@ +/* + * 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.intellij.util.diff; + +import junit.framework.TestCase; + +import java.util.BitSet; + +public class PatienceIntLCSTest extends TestCase { + public void testEqual() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2, 3}, new int[]{1, 2, 3}); + checkChange(change, new int[]{0, 0, 0}, new int[]{0, 0, 0}); + } + + public void testOneAtBegging() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2}, new int[]{1, 3}); + checkChange(change, new int[]{0, 1}, new int[]{0, 1}); + } + + public void testOneAntEnd() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 3}, new int[]{2, 3}); + checkChange(change, new int[]{1, 0}, new int[]{1, 0}); + } + + public void testOneOverAtEnd() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2}, new int[]{1, 2, 3}); + checkChange(change, new int[]{0, 0}, new int[]{0, 0, 1}); + } + + public void testOneOverAtBegging() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2, 3}, new int[]{2, 3}); + checkChange(change, new int[]{1, 0, 0}, new int[]{0, 0}); + } + + public void testSingleMiddle() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2, 3}, new int[]{4, 2, 5}); + checkChange(change, new int[]{1, 0, 1}, new int[]{1, 0, 1}); + } + + public void testAbsolutelyDifferent() throws FilesTooBigForDiffException { + BitSet[] change1 = buildChange(new int[]{1, 2}, new int[]{3, 4}); + checkChange(change1, new int[]{1, 1}, new int[]{1, 1}); + + BitSet[] change2 = buildChange(new int[]{1, 2, 3}, new int[]{4, 5, 6}); + checkChange(change2, new int[]{1, 1, 1}, new int[]{1, 1, 1}); + } + + public void testSingleUniqueMoved() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 1, 2, 2, 10}, new int[]{10, 1, 1, 2, 2}); + checkChange(change, new int[]{1, 1, 1, 1, 0}, new int[]{0, 1, 1, 1, 1}); + } + + public void testSingleFunctionMoved() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 7, 9, 10, 2, 11, 12, 5, 13, 7, 8, 7}, + new int[]{10, 2, 11, 12, 5, 13, 7, 8, 7, 9, 1, 2, 3, 4, 5, 6, 7, 8, 7}); + checkChange(change, new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, + new int[]{0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}); + } + + public void testInnerChunks1() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0}, new int[]{1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1}); + checkChange(change, new int[]{1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}, new int[]{1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}); + } + + public void testInnerChunks2() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{0, 2, 3, 0, 4, 5, 0}, new int[]{1, 2, 1, 3, 4, 5, 1}); + checkChange(change, new int[]{1, 0, 0, 1, 0, 0, 1}, new int[]{1, 0, 1, 0, 0, 0, 1}); + } + + public void testInnerChunks3() throws FilesTooBigForDiffException { + BitSet[] change = buildChange(new int[]{15, 1, 2, 3, 1, 4, 5, 1, 15}, new int[]{13, 1, 2, 1, 3, 4, 5, 1, 13}); + checkChange(change, new int[]{1, 0, 0, 0, 1, 0, 0, 0, 1}, new int[]{1, 0, 0, 1, 0, 0, 0, 0, 1}); + } + + public void testInnerChunks4() throws FilesTooBigForDiffException { + BitSet[] change = + buildChange(new int[]{15, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 15}, new int[]{13, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 13}); + checkChange(change, new int[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, new int[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}); + } + + private static BitSet[] buildChange(int[] first, int[] second) throws FilesTooBigForDiffException { + PatienceIntLCS patienceIntLCS = new PatienceIntLCS(first, second); + patienceIntLCS.execute(); + return patienceIntLCS.getChanges(); + } + + private static void checkChange(BitSet[] change, int[] expected1, int[] expected2) { + for (int i = 0; i < expected1.length; i++) { + assertEquals(change[0].get(i), expected1[i] == 1); + } + for (int i = 0; i < expected2.length; i++) { + assertEquals(change[1].get(i), expected2[i] == 1); + } + } +} diff --git a/platform/util/testSrc/com/intellij/util/diff/ReindexerNewTest.java b/platform/util/testSrc/com/intellij/util/diff/ReindexerNewTest.java new file mode 100644 index 000000000000..c9fe6fbf9463 --- /dev/null +++ b/platform/util/testSrc/com/intellij/util/diff/ReindexerNewTest.java @@ -0,0 +1,125 @@ +/* + * 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.intellij.util.diff; + +import junit.framework.TestCase; + +import java.util.BitSet; +import java.util.Random; + +public class ReindexerNewTest extends TestCase { + public void testAllUnique() throws FilesTooBigForDiffException { + checkCase(new int[]{1, 2, 3}, new int[]{4, 5}); + checkCase(new int[]{}, new int[]{4, 5}); + checkCase(new int[]{1, 2, 3}, new int[]{}); + } + + public void testUniqueBeginning() throws FilesTooBigForDiffException { + checkCase(new int[]{13, 15, 1, 2, 3}, new int[]{17, 1, 3}); + } + + public void testUniqueEnd() throws FilesTooBigForDiffException { + checkCase(new int[]{1, 2, 3, 13, 15}, new int[]{1, 3, 17}); + } + + public void testSingleUniqueMoved() throws FilesTooBigForDiffException { + checkCase(new int[]{1, 1, 2, 2, 10}, new int[]{10, 1, 1, 2, 2}); + } + + public void testSingleFunctionMoved() throws FilesTooBigForDiffException { + checkCase(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 7, 9, 10, 2, 11, 12, 5, 13, 7, 8, 7}, + new int[]{10, 2, 11, 12, 5, 13, 7, 8, 7, 9, 1, 2, 3, 4, 5, 6, 7, 8, 7}); + } + + public void testInnerChunks() throws FilesTooBigForDiffException { + checkCase(new int[]{0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0}, new int[]{1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1}); + checkCase(new int[]{0, 2, 3, 0, 4, 5, 0}, new int[]{1, 2, 1, 3, 4, 5, 1}); + checkCase(new int[]{15, 1, 2, 3, 1, 4, 5, 1, 15}, new int[]{13, 1, 2, 1, 3, 4, 5, 1, 13}); + checkCase(new int[]{15, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 15}, new int[]{13, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 13}); + } + + public void testRandomExample() throws FilesTooBigForDiffException { + int[] ints1 = new int[100]; + int[] ints2 = new int[100]; + Random rng = new Random(); + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 100; j++) { + ints1[j] = rng.nextInt(60); + ints2[j] = rng.nextInt(60); + } + + StringBuilder builder = new StringBuilder(); + builder.append("{"); + for (int j = 0; j < 100; j++) { + builder.append(ints1[j]).append(", "); + } + builder.append("}\n{"); + for (int j = 0; j < 100; j++) { + builder.append(ints2[j]).append(", "); + } + builder.append("}\n"); + + checkCase(ints1, ints2, builder.toString()); + } + } + + public static void checkCase(int[] ints1, int[] ints2) throws FilesTooBigForDiffException { + checkCase(ints1, ints2, null); + } + + public static void checkCase(int[] ints1, int[] ints2, String message) throws FilesTooBigForDiffException { + final BitSet[] reindexChanges = new BitSet[]{new BitSet(), new BitSet()}; + LCSBuilder builder = new LCSBuilder() { + private int myIndex1 = 0; + private int myIndex2 = 0; + + @Override + public void addEqual(int length) { + myIndex1 += length; + myIndex2 += length; + } + + @Override + public void addChange(int first, int second) { + reindexChanges[0].set(myIndex1, myIndex1 + first); + reindexChanges[1].set(myIndex2, myIndex2 + second); + + myIndex1 += first; + myIndex2 += second; + } + }; + + IntLCS lcs = new IntLCS(ints1, ints2); + lcs.execute(); + BitSet[] expectedChanges = lcs.getChanges(); + + Reindexer reindexer = new Reindexer(); + int[][] discarded = reindexer.discardUnique(ints1, ints2); + lcs = new IntLCS(discarded[0], discarded[1]); + lcs.execute(); + BitSet[] changes = lcs.getChanges(); + reindexer.reindex(changes, builder); + + if (message != null) { + assertEquals(message, expectedChanges[0], reindexChanges[0]); + assertEquals(message, expectedChanges[1], reindexChanges[1]); + } + else { + assertEquals(expectedChanges[0], reindexChanges[0]); + assertEquals(expectedChanges[1], reindexChanges[1]); + } + } +} diff --git a/platform/util/testSrc/com/intellij/util/diff/UniqueLCSTest.java b/platform/util/testSrc/com/intellij/util/diff/UniqueLCSTest.java new file mode 100644 index 000000000000..231d961c620c --- /dev/null +++ b/platform/util/testSrc/com/intellij/util/diff/UniqueLCSTest.java @@ -0,0 +1,128 @@ +/* + * 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.intellij.util.diff; + +import junit.framework.TestCase; + +public class UniqueLCSTest extends TestCase { + public void testNoUnique() throws FilesTooBigForDiffException { + int[][] change = buildChange(new int[]{1, 2, 3}, new int[]{4, 5, 6}); + assertNull(change); + + change = buildChange(new int[]{1, 2, 1}, new int[]{1, 3, 1}); + assertNull(change); + + change = buildChange(new int[]{1, 2, 3, 3, 2, 1}, new int[]{1, 2, 3}); + assertNull(change); + + change = buildChange(new int[]{1, 2, 3}, new int[]{1, 2, 3, 3, 2, 1}); + assertNull(change); + + change = buildChange(new int[]{1, 2, 3}, new int[]{1, 2, 3, 3, 2, 1}); + assertNull(change); + + change = buildChange(new int[]{1, 2, 1}, new int[]{2, 1, 2}); + assertNull(change); + + change = buildChange(new int[]{}, new int[]{2, 1, 2}); + assertNull(change); + + change = buildChange(new int[]{1, 2, 2}, new int[]{}); + assertNull(change); + + change = buildChange(new int[]{}, new int[]{}); + assertNull(change); + } + + public void testSingleUnique() throws FilesTooBigForDiffException { + int[][] change = buildChange(new int[]{1}, new int[]{1}); + checkChange(change, new int[]{0}, new int[]{0}); + + change = buildChange(new int[]{5, 1}, new int[]{1}); + checkChange(change, new int[]{1}, new int[]{0}); + + change = buildChange(new int[]{1}, new int[]{2, 1}); + checkChange(change, new int[]{0}, new int[]{1}); + + change = buildChange(new int[]{1}, new int[]{1, 4}); + checkChange(change, new int[]{0}, new int[]{0}); + + change = buildChange(new int[]{1, 3}, new int[]{1}); + checkChange(change, new int[]{0}, new int[]{0}); + + change = buildChange(new int[]{5, 1, 3}, new int[]{2, 1, 4}); + checkChange(change, new int[]{1}, new int[]{1}); + } + + public void testSingleSequence() throws FilesTooBigForDiffException { + int[][] change = buildChange(new int[]{2, 4, 6, 1, 8, 3, 8, 2, 6, 5, 2, 4, 7, 11, 13}, new int[]{1, 10, 2, 3, 5, 7, 11, 13, 12}); + checkChange(change, new int[]{3, 5, 9, 12, 13, 14}, new int[]{0, 3, 4, 5, 6, 7}); + } + + public void testLargestLCS1() throws FilesTooBigForDiffException { + checkMaxSequence(new int[]{1, 2, 3}, new int[]{1, 2, 3}); + } + + public void testLargestLCS2() throws FilesTooBigForDiffException { + checkMaxSequence(new int[]{5, 6, 1, 2, 3}, new int[]{1, 2, 3}); + } + + public void testLargestLCS3() throws FilesTooBigForDiffException { + checkMaxSequence(new int[]{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 7, 15}, new int[]{0, 2, 6, 9, 13, 15}); + } + + public void testLargestLCS4() throws FilesTooBigForDiffException { + checkMaxSequence(new int[]{1, 9, 3, 8, 11, 4, 5, 6, 19, 7}, new int[]{1, 3, 4, 5, 6, 7}); + } + + private static int[][] buildChange(int[] first, int[] second) throws FilesTooBigForDiffException { + UniqueLCS uniqueLCS = new UniqueLCS(first, second); + return uniqueLCS.execute(); + } + + private static void checkChange(int[][] change, int[] expected1, int[] expected2) { + for (int i = 0; i < expected1.length; i++) { + assertEquals(change[0][i], expected1[i]); + } + for (int i = 0; i < expected2.length; i++) { + assertEquals(change[1][i], expected2[i]); + } + } + + private static void checkMaxSequence(int[] sequence, int[] expected) throws FilesTooBigForDiffException { + int max = 0; + for (int i = 0; i < sequence.length; i++) { + max = Math.max(sequence[i] + 1, max); + } + + int[] first = new int[sequence.length]; + int[] second = new int[max + 2]; + + for (int i = 0; i < sequence.length; i++) { + assertEquals("Elements in sequence should be unique", second[sequence[i]], 0); + first[i] = i + 1; + second[sequence[i]] = i + 1; + } + + int[][] result = buildChange(first, second); + + assertEquals(result[0].length, result[1].length); + assertEquals(result[0].length, expected.length); + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i], sequence[result[0][i]]); + } + } +} diff --git a/platform/xdebugger-api/src/com/intellij/xdebugger/XDebugSession.java b/platform/xdebugger-api/src/com/intellij/xdebugger/XDebugSession.java index 342102f1d918..534545847f42 100644 --- a/platform/xdebugger-api/src/com/intellij/xdebugger/XDebugSession.java +++ b/platform/xdebugger-api/src/com/intellij/xdebugger/XDebugSession.java @@ -155,6 +155,13 @@ public interface XDebugSession extends AbstractDebuggerSession { void setPauseActionSupported(boolean isSupported); + @SuppressWarnings("UnusedDeclaration") + @Deprecated + /** + * @deprecated to remove in IDEA 14 + */ + void setAutoInitBreakpoints(boolean value); + void rebuildViews(); void smartStepInto(XSmartStepIntoHandler handler, V variant); diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java index 8accea1d4c36..bb07eea52682 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java @@ -114,6 +114,7 @@ public class XDebugSessionImpl implements XDebugSession { private final Icon myIcon; private volatile boolean breakpointsInitialized; + private boolean autoInitBreakpoints = true; public XDebugSessionImpl(final @NotNull ExecutionEnvironment env, final @NotNull ProgramRunner runner, XDebuggerManagerImpl debuggerManager) { @@ -156,6 +157,11 @@ public class XDebugSessionImpl implements XDebugSession { } } + @Override + public void setAutoInitBreakpoints(boolean value) { + autoInitBreakpoints = value; + } + @Override public void setPauseActionSupported(final boolean isSupported) { myPauseActionSupported = isSupported; @@ -230,7 +236,7 @@ public class XDebugSessionImpl implements XDebugSession { myDebugProcess = process; mySessionData = sessionData; - if (myDebugProcess.checkCanInitBreakpoints()) { + if (autoInitBreakpoints && myDebugProcess.checkCanInitBreakpoints()) { initBreakpoints(); } diff --git a/plugins/ant/src/META-INF/plugin.xml b/plugins/ant/src/META-INF/plugin.xml index 89dbf8854bd2..6917238d231d 100644 --- a/plugins/ant/src/META-INF/plugin.xml +++ b/plugins/ant/src/META-INF/plugin.xml @@ -14,6 +14,8 @@ + + diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/wizard/GradleModuleBuilder.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/wizard/GradleModuleBuilder.java index 6d82f7c2f5ce..4f19cb7bca34 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/wizard/GradleModuleBuilder.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/wizard/GradleModuleBuilder.java @@ -121,7 +121,7 @@ public class GradleModuleBuilder extends AbstractExternalModuleBuilder(this, settingsControl); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppClosureParameterTypeProvider.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppClosureParameterTypeProvider.java index 457c98406cf0..1000a7bb8315 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppClosureParameterTypeProvider.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppClosureParameterTypeProvider.java @@ -1,3 +1,18 @@ +/* + * 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 org.jetbrains.plugins.groovy.gpp; import com.intellij.codeInsight.generation.OverrideImplementUtil; @@ -18,7 +33,9 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpres import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.GroovyExpectedTypesProvider; import org.jetbrains.plugins.groovy.lang.psi.impl.GrTupleType; import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil; +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.AbstractClosureParameterEnhancer; +import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; import java.util.*; @@ -124,7 +141,7 @@ public class GppClosureParameterTypeProvider extends AbstractClosureParameterEnh @Nullable public static PsiType[] findSingleAbstractMethodSignature(@Nullable PsiType type) { - if (type instanceof PsiClassType) { + if (type instanceof PsiClassType && !(TypesUtil.isClassType(type, GroovyCommonClassNames.GROOVY_LANG_CLOSURE))) { List> result = getMethodsToOverrideImplementInInheritor((PsiClassType)type, true); if (result.size() == 1) { return getParameterTypes(result.get(0)); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/signatures/GrClosureSignatureUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/signatures/GrClosureSignatureUtil.java index 48121cf49bb5..5b49ff9c616d 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/signatures/GrClosureSignatureUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/signatures/GrClosureSignatureUtil.java @@ -735,6 +735,14 @@ public class GrClosureSignatureUtil { innerArgs.add(new InnerArg(TypeConversionUtil.erasure(closureArgument.getType()), closureArgument)); } + return mapParametersToArguments(signature, innerArgs, hasNamedArgs, partial, context); + } + + private static ArgInfo[] mapParametersToArguments(@NotNull GrClosureSignature signature, + @NotNull List innerArgs, + boolean hasNamedArgs, + boolean partial, + @NotNull PsiElement context) { final ArgInfo[] innerMap = mapParametersToArguments(signature, innerArgs.toArray(new InnerArg[innerArgs.size()]), new Function() { @Override public PsiType fun(InnerArg o) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrFieldImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrFieldImpl.java index a9e25ea347fe..7e852036dc04 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrFieldImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/GrFieldImpl.java @@ -24,6 +24,7 @@ import com.intellij.psi.presentation.java.JavaPresentationUtil; import com.intellij.psi.search.SearchScope; import com.intellij.psi.stubs.IStubElementType; import com.intellij.ui.LayeredIcon; +import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import icons.JetgroovyIcons; @@ -43,6 +44,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpres import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAccessorMethod; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement; +import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper; import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrAccessorMethodImpl; import org.jetbrains.plugins.groovy.lang.psi.stubs.GrFieldStub; import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrVariableEnhancer; @@ -140,12 +142,23 @@ public class GrFieldImpl extends GrVariableBaseImpl implements GrFi @Override public PsiType getTypeGroovy() { - if (getDeclaredType() == null && getInitializerGroovy() == null) { - final PsiType type = GrVariableEnhancer.getEnhancedType(this); - if (type != null) { - return type; + PsiType type = TypeInferenceHelper.getCurrentContext().getExpressionType(this, new Function() { + @Override + public PsiType fun(GrFieldImpl field) { + if (getDeclaredType() == null && getInitializerGroovy() == null) { + final PsiType type = GrVariableEnhancer.getEnhancedType(field); + if (type != null) { + return type; + } + } + return null; } + }); + + if (type != null) { + return type; } + return super.getTypeGroovy(); } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/CompleteReferenceExpression.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/CompleteReferenceExpression.java index 9bc485e64c2e..31b18c76d689 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/CompleteReferenceExpression.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/CompleteReferenceExpression.java @@ -460,7 +460,7 @@ public class CompleteReferenceExpression { myMethodPointerOperator = place.getDotTokenType() == GroovyTokenTypes.mMEMBER_POINTER; myIsMap = isMap(place); final PsiType thisType = GrReferenceResolveUtil.getQualifierType(place); - mySubstitutorComputer = new SubstitutorComputer(thisType, PsiType.EMPTY_ARRAY, PsiType.EMPTY_ARRAY, true, place, place.getParent()); + mySubstitutorComputer = new SubstitutorComputer(thisType, PsiType.EMPTY_ARRAY, PsiType.EMPTY_ARRAY, place, place.getParent()); } private static boolean shouldSkipPackages(@NotNull GrReferenceExpression place) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceResolveUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceResolveUtil.java index acdd8992c0e3..f500a0e0cf3e 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceResolveUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceResolveUtil.java @@ -24,7 +24,6 @@ import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.untypedUnresolvedAccess.GrUnresolvedAccessInspection; -import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; @@ -36,7 +35,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrRefere import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; -import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement; import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager; import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyResolveResultImpl; import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil; @@ -44,8 +42,6 @@ import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.ClosureParameterEnhan import org.jetbrains.plugins.groovy.lang.psi.util.GdkMethodUtil; import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; -import org.jetbrains.plugins.groovy.lang.resolve.processors.CompletionProcessor; -import org.jetbrains.plugins.groovy.lang.resolve.processors.MethodResolverProcessor; import org.jetbrains.plugins.groovy.lang.resolve.processors.ResolverProcessor; import java.util.List; @@ -63,18 +59,11 @@ public class GrReferenceResolveUtil { static boolean resolveImpl(ResolverProcessor processor, GrReferenceExpression place) { GrExpression qualifier = place.getQualifier(); if (qualifier == null) { - if (processor instanceof MethodResolverProcessor || processor instanceof CompletionProcessor) { - processStaticImports(place.getContainingFile(), processor, ResolveState.initial(), place); - if (processor instanceof MethodResolverProcessor && ((MethodResolverProcessor)processor).hasApplicableCandidates()) { - return false; - } - } - if (!ResolveUtil.treeWalkUp(place, processor, true)) return false; if (!processor.hasCandidates()) { - qualifier = PsiImplUtil.getRuntimeQualifier(place); - if (qualifier != null) { - if (!processQualifier(processor, qualifier, place)) return false; + GrExpression runtimeQualifier = PsiImplUtil.getRuntimeQualifier(place); + if (runtimeQualifier != null) { + if (!processQualifier(processor, runtimeQualifier, place)) return false; } } } @@ -102,18 +91,6 @@ public class GrReferenceResolveUtil { return true; } - private static boolean processStaticImports(PsiFile file, ResolverProcessor processor, ResolveState state, PsiElement place) { - if (file instanceof GroovyFile) { - GrImportStatement[] imports = ((GroovyFile)file).getImportStatements(); - for (GrImportStatement anImport : imports) { - if (anImport.isStatic()) { - if (!anImport.processDeclarations(processor, state, null, place)) return false; - } - } - } - return true; - } - private static boolean processIfJavaLangClass(ResolverProcessor processor, @Nullable PsiType type, GroovyPsiElement resolveContext, diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/ClosureSyntheticParameter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/ClosureSyntheticParameter.java index 2f5102077552..7f104ea2ee4b 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/ClosureSyntheticParameter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/ClosureSyntheticParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -16,11 +16,14 @@ package org.jetbrains.plugins.groovy.lang.psi.impl.synthetic; import com.intellij.navigation.NavigationItem; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.RecursionManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiIntersectionType; import com.intellij.psi.PsiType; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.SearchScope; +import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,12 +31,24 @@ import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; +import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper; import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrVariableEnhancer; /** * @author ven */ public class ClosureSyntheticParameter extends GrLightParameter implements NavigationItem, GrRenameableLightElement { + private static final Function TYPES_CALCULATOR = new Function() { + @Override + public PsiType fun(ClosureSyntheticParameter parameter) { + PsiType typeGroovy = GrVariableEnhancer.getEnhancedType(parameter); + if (typeGroovy instanceof PsiIntersectionType) { + return ((PsiIntersectionType)typeGroovy).getRepresentative(); + } + return typeGroovy; + } + }; + private final GrClosableBlock myClosure; public ClosureSyntheticParameter(GrClosableBlock closure) { @@ -54,11 +69,12 @@ public class ClosureSyntheticParameter extends GrLightParameter implements Navig public PsiType getTypeGroovy() { assert isValid(); - PsiType typeGroovy = GrVariableEnhancer.getEnhancedType(this); - if (typeGroovy instanceof PsiIntersectionType) { - return ((PsiIntersectionType)typeGroovy).getRepresentative(); - } - return typeGroovy; + return RecursionManager.doPreventingRecursion(this, false, new Computable() { + @Override + public PsiType compute() { + return TypeInferenceHelper.getCurrentContext().getExpressionType(ClosureSyntheticParameter.this, TYPES_CALCULATOR); + } + }); } @Nullable diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureParameterEnhancer.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureParameterEnhancer.java index c7c1c23da945..2e88f33faa8d 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureParameterEnhancer.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureParameterEnhancer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -16,9 +16,11 @@ package org.jetbrains.plugins.groovy.lang.psi.typeEnhancers; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.MethodSignature; import com.intellij.psi.util.PsiUtil; import com.intellij.util.containers.hash.HashMap; import com.intellij.util.containers.hash.HashSet; @@ -35,6 +37,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGd import org.jetbrains.plugins.groovy.lang.psi.impl.GrRangeType; import org.jetbrains.plugins.groovy.lang.psi.impl.GrTupleType; import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil; +import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil; import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; import org.jetbrains.plugins.groovy.lang.psi.util.GdkMethodUtil; import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; @@ -129,6 +132,12 @@ public class ClosureParameterEnhancer extends AbstractClosureParameterEnhancer { if (!(parent instanceof GrMethodCall)) { return null; } + + PsiType fromSam = inferParameterTypeFromSAM((GrMethodCall)parent, closure, index); + if (fromSam != null) { + return fromSam; + } + String methodName = findMethodName((GrMethodCall)parent); GrExpression expression = ((GrMethodCall)parent).getInvokedExpression(); @@ -255,6 +264,41 @@ public class ClosureParameterEnhancer extends AbstractClosureParameterEnhancer { return null; } + @Nullable + private static PsiType inferParameterTypeFromSAM(@NotNull GrMethodCall methodCall, @NotNull GrClosableBlock closure, int index) { + if (!ClosureToSamConverter.isSamConversionAllowed(methodCall)) return null; + + GroovyResolveResult resolveResult = methodCall.advancedResolve(); + PsiElement resolved = resolveResult.getElement(); + + if (!(resolved instanceof PsiMethod)) return null; + + + + Map> map = GrClosureSignatureUtil.mapArgumentsToParameters(resolveResult, methodCall, false, true, + methodCall.getNamedArguments(), + methodCall.getExpressionArguments(), + methodCall.getClosureArguments()); + if (map == null) return null; + + Pair samParameter = map.get(closure); + assert samParameter != null; + + PsiType samTypeSubstituted = samParameter.getSecond(); + if (!(samTypeSubstituted instanceof PsiClassType)) return null; + + MethodSignature samSignature = ClosureToSamConverter.findSAMSignature(samTypeSubstituted); + if (samSignature == null) return null; + + PsiType[] parameterTypes = samSignature.getParameterTypes(); + + if (index >= parameterTypes.length) { + return null; + } + + return parameterTypes[index]; + } + @Nullable private static PsiType getEntryForMap(@Nullable PsiType map, @NotNull final Project project, @NotNull final GlobalSearchScope scope) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java index 7516dece6207..32faeb768041 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java @@ -27,6 +27,8 @@ import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrSignature; import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType; import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil; +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; +import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; import org.jetbrains.plugins.groovy.util.LightCacheKey; import java.util.Collection; @@ -44,12 +46,19 @@ public class ClosureToSamConverter extends GrTypeConverter { @Override public Boolean isConvertible(@NotNull PsiType ltype, @NotNull PsiType rtype, @NotNull final GroovyPsiElement context) { - if (rtype instanceof GrClosureType && ltype instanceof PsiClassType && GroovyConfigUtils.getInstance().isVersionAtLeast(context, GroovyConfigUtils.GROOVY2_2)) { + if (rtype instanceof GrClosureType && + ltype instanceof PsiClassType && + isSamConversionAllowed(context) && + !TypesUtil.isClassType(ltype, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) { MethodSignature signature = findSAMSignature(ltype); if (signature != null) { final PsiType[] samParameterTypes = signature.getParameterTypes(); GrSignature closureSignature = ((GrClosureType)rtype).getSignature(); + + boolean raw = ((PsiClassType)ltype).isRaw(); + if (raw) return true; + if (GrClosureSignatureUtil.isSignatureApplicable(closureSignature, samParameterTypes, context)) { return true; } @@ -59,6 +68,10 @@ public class ClosureToSamConverter extends GrTypeConverter { return null; } + public static boolean isSamConversionAllowed(PsiElement context) { + return GroovyConfigUtils.getInstance().isVersionAtLeast(context, GroovyConfigUtils.GROOVY2_2); + } + @Nullable public static MethodSignature findSingleAbstractMethod(@NotNull PsiClass aClass, @NotNull PsiSubstitutor substitutor) { MethodSignature signature; @@ -98,6 +111,8 @@ public class ClosureToSamConverter extends GrTypeConverter { @Nullable public static MethodSignature findSAMSignature(@Nullable PsiType type) { if (type instanceof PsiClassType) { + if (TypesUtil.isClassType(type, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) return null; + PsiClassType.ClassResolveResult result = ((PsiClassType)type).resolveGenerics(); PsiClass aClass = result.getElement(); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorResolverProcessor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorResolverProcessor.java index 879f839da505..43d0e3de9460 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorResolverProcessor.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorResolverProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -50,7 +50,7 @@ public class AccessorResolverProcessor extends MethodResolverProcessor { myPropertyName = propertyName; mySearchForGetter = searchForGetter; - mySubstitutorComputer = byShape ? null : new SubstitutorComputer(thisType, PsiType.EMPTY_ARRAY, typeArguments, false, place, myPlace); + mySubstitutorComputer = byShape ? null : new SubstitutorComputer(thisType, PsiType.EMPTY_ARRAY, typeArguments, place, myPlace); } public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java index c929a20cd48c..9e94b5bfeedc 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java @@ -88,7 +88,7 @@ public class MethodResolverProcessor extends ResolverProcessor implements GrMeth myAllVariants = allVariants; myByShape = byShape; - mySubstitutorComputer = new SubstitutorComputer(myThisType, myArgumentTypes, typeArguments, myAllVariants, myPlace, myPlace.getParent()); + mySubstitutorComputer = new SubstitutorComputer(myThisType, myArgumentTypes, typeArguments, myPlace, myPlace.getParent()); myTypedContext = GppTypeConverter.hasTypedContext(myPlace); } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java index 01242d5a7315..6e74d689114a 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java @@ -23,7 +23,6 @@ import com.intellij.util.containers.hash.HashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; -import org.jetbrains.plugins.groovy.config.GroovyConfigUtils; import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner; import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrClosureSignature; @@ -59,8 +58,6 @@ public class SubstitutorComputer { @Nullable private final PsiType[] myArgumentTypes; private final PsiType[] myTypeArguments; - private final boolean myAllVariants; - private final GrControlFlowOwner myFlowOwner; private final PsiElement myPlaceToInferContext; private PsiResolveHelper myHelper; @@ -69,13 +66,11 @@ public class SubstitutorComputer { public SubstitutorComputer(PsiType thisType, @Nullable PsiType[] argumentTypes, PsiType[] typeArguments, - boolean allVariants, PsiElement place, PsiElement placeToInferContext) { myThisType = thisType; myArgumentTypes = argumentTypes; myTypeArguments = typeArguments; - myAllVariants = allVariants; myPlace = place; myPlaceToInferContext = placeToInferContext; @@ -164,8 +159,7 @@ public class SubstitutorComputer { final GrClosureSignature signature = GrClosureSignatureUtil.createSignature(method, partialSubstitutor); final GrClosureParameter[] params = signature.getParameters(); - final GrClosureSignatureUtil.ArgInfo[] argInfos = - GrClosureSignatureUtil.mapArgTypesToParameters(erasedSignature, argTypes, myPlace, myAllVariants); + final GrClosureSignatureUtil.ArgInfo[] argInfos = GrClosureSignatureUtil.mapArgTypesToParameters(erasedSignature, argTypes, myPlace, true); if (argInfos == null) return partialSubstitutor; int max = Math.max(params.length, argTypes.length); @@ -208,12 +202,15 @@ public class SubstitutorComputer { @Nullable private PsiType handleConversion(@Nullable PsiType paramType, @Nullable PsiType argType) { - if (GroovyConfigUtils.getInstance().isVersionAtLeast(myPlace, GroovyConfigUtils.GROOVY2_2_2) && - InheritanceUtil.isInheritor(argType, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) { + if (ClosureToSamConverter.isSamConversionAllowed(myPlace) && + InheritanceUtil.isInheritor(argType, GroovyCommonClassNames.GROOVY_LANG_CLOSURE) && + !TypesUtil.isClassType(paramType, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) { PsiType converted = handleConversionOfSAMType(paramType, (PsiClassType)argType); if (converted != null) { return converted; } + + return argType; } if (!TypesUtil.isAssignable(TypeConversionUtil.erasure(paramType), argType, myPlace) && @@ -242,7 +239,9 @@ public class SubstitutorComputer { closureArgs, LanguageLevel.JDK_1_7); - return JavaPsiFacade.getElementFactory(myPlace.getProject()).createType(samClass, substitutor); + if (!substitutor.getSubstitutionMap().isEmpty()) { + return JavaPsiFacade.getElementFactory(myPlace.getProject()).createType(samClass, substitutor); + } } } } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy index 6192a38c89e1..fcc821a1323f 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -157,7 +157,8 @@ class GroovyStressPerformanceTest extends LightGroovyTestCase { text = "foo(it) { $text }" } myFixture.enableInspections(new MissingReturnInspection()) - measureHighlighting("def foo(T t, Closure cl) {}\n" + text, 10000) + + measureHighlighting("def void foo(T t, Closure cl) {}\n$text", 10000) } public void testDeeplyNestedClosuresInGenericCalls2() { @@ -167,7 +168,7 @@ class GroovyStressPerformanceTest extends LightGroovyTestCase { text = "foo(it) { $text }" } myFixture.enableInspections(new MissingReturnInspection()) - measureHighlighting("def foo(T t, Closure cl) {}\n" + text, 10000) + measureHighlighting("def void foo(T t, Closure cl) {}\n$text", 10000) } public void testManyAnnotatedScriptVariables() { diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy index 44641725ae8f..0d2e739d769c 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -65,7 +65,8 @@ interface A { A a1 = {print 1} A a2 = {String s -> print 1} A a3 = {int s -> print 1} -A a4 = {int s -> print 1} +A a4 = {int s -> print 1} +A a7 = {int s, String y -> print 1} A a5 = { print 1} A a6 = {x -> print 1} ''') diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy23HighlightingTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy23HighlightingTest.groovy new file mode 100644 index 000000000000..3df6895053eb --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy23HighlightingTest.groovy @@ -0,0 +1,86 @@ +/* + * 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 org.jetbrains.plugins.groovy.lang.highlighting + +import com.intellij.codeInspection.InspectionProfileEntry +import com.intellij.testFramework.LightProjectDescriptor +import org.jetbrains.annotations.NotNull +import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor +import org.jetbrains.plugins.groovy.codeInspection.assignment.GroovyAssignabilityCheckInspection +import org.jetbrains.plugins.groovy.codeInspection.untypedUnresolvedAccess.GrUnresolvedAccessInspection + +/** + * Created by Max Medvedev on 17/02/14 + */ +class Groovy23HighlightingTest extends GrHighlightingTestBase { + + @Override + @NotNull + protected LightProjectDescriptor getProjectDescriptor() { + return GroovyLightProjectDescriptor.GROOVY_2_3; + } + + void testSam1() { + testHighlighting(''' +interface Action { + void execute(T t, X x) +} + +public void exec(T t, Action f, X x) { +} + +def foo() { + exec('foo', { String t, Integer x -> ; }, 1) + exec('foo', { Integer t, Integer x -> ; }, 1) +} +''') + } + + void testSam2() { + testHighlighting(''' +interface Action { + void execute(T t) +} + +public void exec(T t, Action f) { +} + +def foo() { + exec('foo') {print it.toUpperCase() ;print 2 } + exec('foo') {print it.intValue() ;print 2 } +} +''') + } + + void testSam3() { + testHighlighting(''' + interface Action { + void execute(T t, X x) +} + +public void exec(T t, Action f, X x) { + f.execute(t, x) +} + +def foo() { + exec('foo', { String s, Integer x -> print s + x }, 1) + exec('foo', { Integer s, Integer x -> print 9 }, 1) +} +''') + } + + final InspectionProfileEntry[] customInspections = [new GroovyAssignabilityCheckInspection(), new GrUnresolvedAccessInspection()] +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy index 343e6d96e501..3af7280e48e4 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -906,7 +906,7 @@ class A { def resolved = ref.resolve() assertInstanceOf resolved, PsiMethod - assertEquals 'Other', resolved.containingClass.name + assertEquals 'A', resolved.containingClass.name } public void testInapplicableStaticallyImportedMethodsVsCurrentClassMethod() { diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy index 731023d74549..d86e176ddd83 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInference2_3Test.groovy @@ -137,4 +137,21 @@ class Thing { ''', 'java.lang.Integer') } + + void testSamInference4() { + doTest(''' +interface Action { + void execute(T t) +} + +public void exec(T t, Action f) { +} + + +def foo() { + exec('foo') {print it.toUpperCase() ;print 2 } +} + +''', 'java.lang.String') + } } diff --git a/plugins/maven/maven3-server-impl/lib/maven3/lib/maven-model-builder-3.0.5.jar b/plugins/maven/maven3-server-impl/lib/maven3/lib/maven-model-builder-3.0.5.jar index 780d8d38c291..1ff1421547ca 100644 Binary files a/plugins/maven/maven3-server-impl/lib/maven3/lib/maven-model-builder-3.0.5.jar and b/plugins/maven/maven3-server-impl/lib/maven3/lib/maven-model-builder-3.0.5.jar differ diff --git a/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/Maven3ServerEmbedderImpl.java b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/Maven3ServerEmbedderImpl.java index 80972f72a694..ae454210c909 100644 --- a/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/Maven3ServerEmbedderImpl.java +++ b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/Maven3ServerEmbedderImpl.java @@ -34,6 +34,7 @@ import org.apache.maven.model.Activation; import org.apache.maven.model.Model; import org.apache.maven.model.Plugin; import org.apache.maven.model.Profile; +import org.apache.maven.model.interpolation.ModelInterpolator; import org.apache.maven.model.profile.DefaultProfileInjector; import org.apache.maven.plugin.LegacySupport; import org.apache.maven.plugin.internal.PluginDependenciesResolver; @@ -91,6 +92,9 @@ import java.util.concurrent.atomic.AtomicReference; maven-core: org.jetbrains.idea.maven.server.embedder.CustomMaven3ArtifactFactory <-> org.apache.maven.artifact.factory.DefaultArtifactFactory org.jetbrains.idea.maven.server.embedder.CustomPluginDescriptorCache <-> org.apache.maven.plugin.DefaultPluginDescriptorCache + + maven-model-builder: + org.jetbrains.idea.maven.server.embedder.CustomMaven3ModelInterpolator2 <-> org.apache.maven.model.interpolation.StringSearchModelInterpolator */ public class Maven3ServerEmbedderImpl extends MavenRemoteObject implements MavenServerEmbedder { @NotNull private final DefaultPlexusContainer myContainer; @@ -354,9 +358,19 @@ public class Maven3ServerEmbedderImpl extends MavenRemoteObject implements Maven // copied from DefaultMavenProjectBuilder.buildWithDependencies ProjectBuilder builder = getComponent(ProjectBuilder.class); - // Don't use build(File projectFile, ProjectBuildingRequest request) , because it don't use cache !!!!!!!! (see http://devnet.jetbrains.com/message/5500218) - List results = - builder.build(Collections.singletonList(new File(file.getPath())), false, request.getProjectBuildingRequest()); + CustomMaven3ModelInterpolator2 modelInterpolator = (CustomMaven3ModelInterpolator2)getComponent(ModelInterpolator.class); + + String savedLocalRepository = modelInterpolator.getLocalRepository(); + modelInterpolator.setLocalRepository(request.getLocalRepositoryPath().getAbsolutePath()); + List results; + + try { + // Don't use build(File projectFile, ProjectBuildingRequest request) , because it don't use cache !!!!!!!! (see http://devnet.jetbrains.com/message/5500218) + results = builder.build(Collections.singletonList(new File(file.getPath())), false, request.getProjectBuildingRequest()); + } + finally { + modelInterpolator.setLocalRepository(savedLocalRepository); + } ProjectBuildingResult buildingResult = results.get(0); diff --git a/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/embedder/CustomMaven3ModelInterpolator2.java b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/embedder/CustomMaven3ModelInterpolator2.java new file mode 100644 index 000000000000..534ed3dc2fb6 --- /dev/null +++ b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/embedder/CustomMaven3ModelInterpolator2.java @@ -0,0 +1,59 @@ +/* + * 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 org.jetbrains.idea.maven.server.embedder; + +import org.apache.maven.model.Model; +import org.apache.maven.model.building.ModelBuildingRequest; +import org.apache.maven.model.building.ModelProblemCollector; +import org.apache.maven.model.interpolation.ModelInterpolator; +import org.apache.maven.model.interpolation.StringSearchModelInterpolator; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.interpolation.SingleResponseValueSource; +import org.codehaus.plexus.interpolation.ValueSource; + +import java.io.File; +import java.util.List; + +/** + * @author Sergey Evdokimov + */ +@Component( role = ModelInterpolator.class ) +public class CustomMaven3ModelInterpolator2 extends StringSearchModelInterpolator { + + private String localRepository; + + @Override + protected List createValueSources(Model model, + File projectDir, + ModelBuildingRequest config, + ModelProblemCollector problems) { + List res = super.createValueSources(model, projectDir, config, problems); + + if (localRepository != null) { + res.add(new SingleResponseValueSource("settings.localRepository", localRepository)); + } + + return res; + } + + public String getLocalRepository() { + return localRepository; + } + + public void setLocalRepository(String localRepository) { + this.localRepository = localRepository; + } +} diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenJUnitPatcher.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenJUnitPatcher.java index d99af59a0e8d..0dbfc4f5e21b 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenJUnitPatcher.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenJUnitPatcher.java @@ -61,7 +61,7 @@ public class MavenJUnitPatcher extends JUnitPatcher { } Element systemPropertyVariables = config.getChild("systemPropertyVariables"); - if (systemPropertyVariables != null) { + if (systemPropertyVariables != null && isEnabled("systemPropertyVariables")) { for (Element element : systemPropertyVariables.getChildren()) { String propertyName = element.getName(); @@ -72,7 +72,7 @@ public class MavenJUnitPatcher extends JUnitPatcher { } Element environmentVariables = config.getChild("environmentVariables"); - if (environmentVariables != null) { + if (environmentVariables != null && isEnabled("environmentVariables")) { for (Element element : environmentVariables.getChildren()) { String variableName = element.getName(); @@ -83,11 +83,15 @@ public class MavenJUnitPatcher extends JUnitPatcher { } Element argLine = config.getChild("argLine"); - if (argLine != null) { + if (argLine != null && isEnabled("argLine")) { String value = argLine.getTextTrim(); if (StringUtil.isNotEmpty(value)) { javaParameters.getVMParametersList().addParametersString(value); } } } + + private static boolean isEnabled(String s) { + return !Boolean.valueOf(System.getProperty("idea.maven.surefire.disable." + s)); + } } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/wizards/MavenModuleBuilder.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/wizards/MavenModuleBuilder.java index 0e01064df1f2..1561223eaa68 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/wizards/MavenModuleBuilder.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/wizards/MavenModuleBuilder.java @@ -218,7 +218,7 @@ public class MavenModuleBuilder extends ModuleBuilder implements SourcePathsBuil @Nullable @Override - public ModuleWizardStep getCustomOptionsStep(Disposable parentDisposable) { + public ModuleWizardStep getCustomOptionsStep(WizardContext context, Disposable parentDisposable) { MavenArchetypesStep step = new MavenArchetypesStep(this, null); Disposer.register(parentDisposable, step); return step; diff --git a/python/build/python_plugin_build.gant b/python/build/python_plugin_build.gant index 0383e3e8b26e..261313a5afea 100644 --- a/python/build/python_plugin_build.gant +++ b/python/build/python_plugin_build.gant @@ -149,6 +149,10 @@ target(name: "compile", description: "Compile module python") { fileset(file: "${pluginHome}/pluginResources/META-INF/*.xml") } + ant.replace(file: "${classesDir}/META-INF/plugin.xml") { + replacefilter(token: "@@BUILD_NUMBER@@", value: "${buildNumber}") + } + ant.replaceregexp(file: "${classesDir}/META-INF/plugin.xml", match: "since-build=\"\\d+\\.\\d+\"", replace: "since-build=\"${ideaBuildNumber}\"") diff --git a/python/helpers/pydev/pydev_monkey.py b/python/helpers/pydev/pydev_monkey.py index 8dd94ab1842d..ed6fea53ff58 100644 --- a/python/helpers/pydev/pydev_monkey.py +++ b/python/helpers/pydev/pydev_monkey.py @@ -4,7 +4,7 @@ import sys import pydev_log import traceback -helpers = os.path.dirname(__file__) +helpers = os.path.dirname(__file__).replace('\\', '/') def is_python(path): if path.endswith("'") or path.endswith('"'): diff --git a/python/ide/src/com/jetbrains/python/PyCharmInitialConfigurator.java b/python/ide/src/com/jetbrains/python/PyCharmInitialConfigurator.java index e87d82106fbb..091a67005a75 100644 --- a/python/ide/src/com/jetbrains/python/PyCharmInitialConfigurator.java +++ b/python/ide/src/com/jetbrains/python/PyCharmInitialConfigurator.java @@ -55,10 +55,15 @@ public class PyCharmInitialConfigurator { propertiesComponent.setValue("PyCharm.InitialConfiguration.V3", "true"); UISettings.getInstance().SHOW_MEMORY_INDICATOR = false; final String ignoredFilesList = fileTypeManager.getIgnoredFilesList(); - ApplicationManager.getApplication().runWriteAction(new Runnable() { + ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { - FileTypeManager.getInstance().setIgnoredFilesList(ignoredFilesList + ";*$py.class"); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + FileTypeManager.getInstance().setIgnoredFilesList(ignoredFilesList + ";*$py.class"); + } + }); } }); } diff --git a/python/pluginResources/META-INF/plugin.xml b/python/pluginResources/META-INF/plugin.xml index a26af68b23d2..e3aa06f00a11 100644 --- a/python/pluginResources/META-INF/plugin.xml +++ b/python/pluginResources/META-INF/plugin.xml @@ -6,7 +6,7 @@ Python Community Edition Smart editing for Python scripts - 3.1 + 3.1.1.134.@@BUILD_NUMBER@@ com.intellij.modules.java diff --git a/python/resources/tips/tips.xml b/python/resources/tips/tips.xml deleted file mode 100644 index 5ef93412ac62..000000000000 --- a/python/resources/tips/tips.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/src/META-INF/IdeTipsAndTricks.xml b/python/src/META-INF/IdeTipsAndTricks.xml new file mode 100644 index 000000000000..41a5febdab94 --- /dev/null +++ b/python/src/META-INF/IdeTipsAndTricks.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/python/src/META-INF/pycharm-core.xml b/python/src/META-INF/pycharm-core.xml index a232079090a0..dc95bd030198 100644 --- a/python/src/META-INF/pycharm-core.xml +++ b/python/src/META-INF/pycharm-core.xml @@ -1,5 +1,6 @@ + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 2a13ad0e722e..7bc546972af3 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -560,7 +560,7 @@ refactoring.extract.method.error.cannot.perform.refactoring.with.local=Cannot pe # extract superclass refactoring.extract.super.target.path.outside.roots=Target directory is outside the project.
Must be within content roots -refactoring.extract.super.name.0.must.be.ident=Name ''{0}'' is invalid.
Must be a valid Python identifier +refactoring.extract.super.name.0.must.be.ident=Name ''{0}'' is invalid. Must be a valid Python identifier refactoring.extract.super.class.no.members.allowed=None of members could be extracted # move diff --git a/python/src/com/jetbrains/python/inspections/PyAssignmentToLoopOrWithParameterInspection.java b/python/src/com/jetbrains/python/inspections/PyAssignmentToLoopOrWithParameterInspection.java index aefbc343b2d5..2efaf9868243 100644 --- a/python/src/com/jetbrains/python/inspections/PyAssignmentToLoopOrWithParameterInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyAssignmentToLoopOrWithParameterInspection.java @@ -132,17 +132,7 @@ public class PyAssignmentToLoopOrWithParameterInspection extends PyInspection { if (element instanceof PySubscriptionExpression) { element = ((PySubscriptionExpression)element).getRootOperand(); } - while (true) { - PsiReference reference = element.getReference(); - if (reference == null) { - break; - } - PsiElement resolve = reference.resolve(); - if (resolve == null || resolve.equals(element) || !PyUtil.inSameFile(resolve, element)) { - break; - } - element = resolve; - } + element = PyUtil.resolveToTheTop(element); return element; } diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index bb201cab324d..8af009bf41ed 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -700,6 +700,38 @@ public class PyUtil { return PyElementGenerator.getInstance(element.getProject()).createNameIdentifier(name, LanguageLevel.forElement(element)); } + /** + * Finds element declaration by resolving its references top the top but not further than file (to prevent unstubing) + * @param element element to resolve + * @return its declaration + */ + @NotNull + public static PsiElement resolveToTheTop(@NotNull final PsiElement elementToResolve) { + PsiElement currentElement = elementToResolve; + while (true) { + final PsiReference reference = currentElement.getReference(); + if (reference == null) { + break; + } + final PsiElement resolve = reference.resolve(); + if ((resolve == null) || resolve.equals(currentElement) || !inSameFile(resolve, currentElement)) { + break; + } + currentElement = resolve; + } + return currentElement; + } + + /** + * Gets class init method + * @param pyClass class where to find init + * @return class init method if any + */ + @Nullable + public static PyFunction getInitMethod(@NotNull final PyClass pyClass) { + return pyClass.findMethodByName(PyNames.INIT, false); + } + public static class KnownDecoratorProviderHolder { public static PyKnownDecoratorProvider[] KNOWN_DECORATOR_PROVIDERS = Extensions.getExtensions(PyKnownDecoratorProvider.EP_NAME); diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java index 55955e0622b4..53dea09e4f5c 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java @@ -24,23 +24,34 @@ import com.intellij.util.ArrayUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.PythonFileType; import com.jetbrains.python.psi.*; + import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.regex.Pattern; /** * @author yole */ public class PyFunctionBuilder { + private static final String COMMENTS_BOUNDARY = "\"\"\""; + private static final Pattern INDENT_REMOVE_PATTERN = Pattern.compile("^\\s+", Pattern.MULTILINE); private final String myName; private final List myParameters = new ArrayList(); private final List myStatements = new ArrayList(); private final List myDecorators = new ArrayList(); private String myAnnotation = null; + private String[] myDocStringLines = null; - //TODO: Doc + /** + * Creates builder copying signature and doc from another one. + * @param source what to copy + * @param decoratorsToCopyIfExist list of decorator names to be copied to new function. + * @return builder configured by this function + */ @NotNull public static PyFunctionBuilder copySignature(@NotNull final PyFunction source, @NotNull final String... decoratorsToCopyIfExist) { final String name = source.getName(); @@ -62,9 +73,26 @@ public class PyFunctionBuilder { } } } + final String docString = source.getDocStringValue(); + if (docString != null) { + functionBuilder.docString(docString); + } return functionBuilder; } + /** + * Adds docstring to function. Provide doc with out of comment blocks. + * @param docString doc + */ + public void docString(@NotNull final String docString) { + myDocStringLines = StringUtil.splitByLines(removeIndent(docString)); + } + + @NotNull + private String removeIndent(@NotNull final String string) { + return INDENT_REMOVE_PATTERN.matcher(string).replaceAll(""); + } + public PyFunctionBuilder(String name) { myName = name; } @@ -118,6 +146,16 @@ public class PyFunctionBuilder { } builder.append(":"); List statements = myStatements.isEmpty() ? Collections.singletonList(PyNames.PASS) : myStatements; + + if (myDocStringLines != null) { + final List comments = new ArrayList(myDocStringLines.length + 2); + comments.add(COMMENTS_BOUNDARY); + comments.addAll(Arrays.asList(myDocStringLines)); + comments.add(COMMENTS_BOUNDARY); + statements = new ArrayList(statements); + statements.addAll(0, comments); + } + final CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getInstance(project).getCurrentSettings(); int indentSize = codeStyleSettings.getIndentOptions(PythonFileType.INSTANCE).INDENT_SIZE; String indent = StringUtil.repeatSymbol(' ', indentSize); @@ -130,4 +168,11 @@ public class PyFunctionBuilder { public void decorate(String decoratorName) { myDecorators.add("@" + decoratorName); } + + @NotNull + private static String getIndent(@NotNull final Project project) { + final CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getInstance(project).getCurrentSettings(); + final int indentSize = codeStyleSettings.getIndentOptions(PythonFileType.INSTANCE).INDENT_SIZE; + return StringUtil.repeatSymbol(' ', indentSize); + } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassHelper.java b/python/src/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassHelper.java index 2e3d32dc9ab2..52657c67d9a8 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassHelper.java +++ b/python/src/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassHelper.java @@ -60,31 +60,42 @@ public final class PyExtractSuperclassHelper { @NotNull Collection> selectedMemberInfos, final String superBaseName, final String targetFile) { - //We will need to change it probably while param may be read-only //noinspection AssignmentToMethodParameter selectedMemberInfos = new ArrayList>(selectedMemberInfos); - // 'object' superclass is always pulled up, even if not selected explicitly - if (MembersManager.findMember(selectedMemberInfos, ALLOW_OBJECT) == null) { - final PyMemberInfo object = MembersManager.findMember(clazz, ALLOW_OBJECT); - if (object != null) { - selectedMemberInfos.add(object); + + // PY-12171 + final PyMemberInfo objectMember = MembersManager.findMember(selectedMemberInfos, ALLOW_OBJECT); + if (LanguageLevel.forElement(clazz).isPy3K()) { + // Remove object from list if Py3 + if (objectMember != null) { + selectedMemberInfos.remove(objectMember); + } + } else { + // Always add object if < Py3 + if (objectMember == null) { + final PyMemberInfo object = MembersManager.findMember(clazz, ALLOW_OBJECT); + if (object != null) { + selectedMemberInfos.add(object); + } } } - final Project project = clazz.getProject(); final String text = "class " + superBaseName + ":\n pass" + "\n"; PyClass newClass = PyElementGenerator.getInstance(project).createFromText(LanguageLevel.getDefault(), PyClass.class, text); newClass = placeNewClass(project, newClass, clazz, targetFile); - MembersManager.moveAllMembers(selectedMemberInfos, clazz, newClass); + if (! newClass.getContainingFile().equals(clazz.getContainingFile())) { + PyClassRefactoringUtil.optimizeImports(clazz.getContainingFile()); // To remove unneeded imports only if user used different file + } PyClassRefactoringUtil.addSuperclasses(project, clazz, null, newClass); + } - private static PyClass placeNewClass(Project project, PyClass newClass, @NotNull PyClass clazz, String targetFile) { + private static PyClass placeNewClass(final Project project, PyClass newClass, @NotNull final PyClass clazz, final String targetFile) { VirtualFile file = VirtualFileManager.getInstance() .findFileByUrl(ApplicationManagerEx.getApplicationEx().isUnitTestMode() ? targetFile : VfsUtilCore.pathToUrl(targetFile)); // file is the same as the source diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java index d62a819c735b..ba6cb0c2f497 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java @@ -1,5 +1,8 @@ package com.jetbrains.python.refactoring.classes.membersManager; +import com.google.common.collect.Collections2; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.NotNullPredicate; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyFunctionBuilder; @@ -14,6 +17,9 @@ import java.util.List; * @author Ilya.Kazakevich */ class InstanceFieldsManager extends FieldsManager { + + // PY-12170 + InstanceFieldsManager() { super(false); } @@ -21,37 +27,37 @@ class InstanceFieldsManager extends FieldsManager { @Override protected Collection moveAssignments(@NotNull final PyClass from, - @NotNull final Collection statements, - @NotNull final PyClass... to) { + @NotNull final Collection statements, + @NotNull final PyClass... to) { //TODO: Copy/paste with ClassFieldsManager. Move to parent? final List result = new ArrayList(); for (final PyClass destClass : to) { result.addAll(copyInstanceFields(statements, destClass)); } - - deleteElements(statements); - - final PyFunction fromInitMethod = from.findMethodByName(PyNames.INIT, false); - if (fromInitMethod != null) { + // Delete only declarations made in __init__ to prevent PY-12170 + final PyFunction fromInitMethod = PyUtil.getInitMethod(from); + if (fromInitMethod != null) { // If class has no init method that means all its fields declared in other methods, so nothing to remove + deleteElements(Collections2.filter(statements, new InitsOnly(fromInitMethod))); //We can't leave class constructor with empty body PyClassRefactoringUtil.insertPassIfNeeded(fromInitMethod); } return result; } - /** + /** * Copies class' fields in form of assignments (instance fields) to another class. - * Creates init method if there is no any + * Creates init method if there is no any + * * @param members assignments to copy - * @param to destination + * @param to destination * @return newly created fields */ @NotNull private static List copyInstanceFields(@NotNull final Collection members, @NotNull final PyClass to) { //We need __init__ method, and if there is no any -- we need to create it - PyFunction toInitMethod = to.findMethodByName(PyNames.INIT, false); + PyFunction toInitMethod = PyUtil.getInitMethod(to); if (toInitMethod == null) { toInitMethod = createInitMethod(to); } @@ -61,6 +67,7 @@ class InstanceFieldsManager extends FieldsManager { /** * Creates init method and adds it to certain class. + * * @param to Class where method should be added * @return newly created method */ @@ -83,4 +90,24 @@ class InstanceFieldsManager extends FieldsManager { protected List getFieldsByClass(@NotNull final PyClass pyClass) { return pyClass.getInstanceAttributes(); } + + private static class InitsOnly extends NotNullPredicate { + @NotNull + private final PyFunction myInitMethod; + + private InitsOnly(@NotNull final PyFunction initMethod) { + myInitMethod = initMethod; + } + + @Override + protected boolean applyNotNull(@NotNull final PyAssignmentStatement input) { + final PyExpression expression = input.getLeftHandSideExpression(); + if (expression == null) { + return false; + } + + final PyFunction functionWhereDeclared = PsiTreeUtil.getParentOfType(PyUtil.resolveToTheTop(expression), PyFunction.class); + return myInitMethod.equals(functionWhereDeclared); + } + } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/PyMembersRefactoringBaseProcessor.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/PyMembersRefactoringBaseProcessor.java index 2744cf0ae9aa..983deb65f601 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/PyMembersRefactoringBaseProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/PyMembersRefactoringBaseProcessor.java @@ -7,6 +7,7 @@ import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewDescriptor; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyElement; +import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -80,5 +81,6 @@ public abstract class PyMembersRefactoringBaseProcessor extends BaseRefactoringP destinations.add(((PyUsageInfo)usage).getTo()); } MembersManager.moveAllMembers(myMembersToMove, myFrom, destinations.toArray(new PyClass[destinations.size()])); + PyClassRefactoringUtil.optimizeImports(myFrom.getContainingFile()); // To remove unneeded imports } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/SuperClassesManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/SuperClassesManager.java index 67a1f6eebdd3..82ce209931f7 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/SuperClassesManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/SuperClassesManager.java @@ -1,9 +1,11 @@ package com.jetbrains.python.refactoring.classes.membersManager; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; import com.intellij.refactoring.RefactoringBundle; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyElement; -import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.NotNullPredicate; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.*; import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil; import com.jetbrains.python.refactoring.classes.ui.PyClassCellRenderer; import org.jetbrains.annotations.NotNull; @@ -19,6 +21,9 @@ import java.util.List; * @author Ilya.Kazakevich */ class SuperClassesManager extends MembersManager { + + private static final NoFakeSuperClasses NO_FAKE_SUPER_CLASSES = new NoFakeSuperClasses(); + SuperClassesManager() { super(PyClass.class); } @@ -27,19 +32,26 @@ class SuperClassesManager extends MembersManager { @NotNull @Override protected List getMembersCouldBeMoved(@NotNull final PyClass pyClass) { - return Arrays.asList(pyClass.getSuperClasses()); + return Lists.newArrayList(Collections2.filter(Arrays.asList(pyClass.getSuperClasses()), NO_FAKE_SUPER_CLASSES)); } @Override - protected Collection moveMembers(@NotNull final PyClass from, @NotNull final Collection> members, @NotNull final PyClass... to) { + protected Collection moveMembers(@NotNull final PyClass from, + @NotNull final Collection> members, + @NotNull final PyClass... to) { final Collection elements = fetchElements(members); for (final PyClass destClass : to) { PyClassRefactoringUtil.addSuperclasses(from.getProject(), destClass, elements.toArray(new PyClass[members.size()])); } for (final PyExpression expression : from.getSuperClassExpressions()) { + // Remove all superclass expressions that point to class from memberinfo + if (!(expression instanceof PyQualifiedExpression)) { + continue; + } + final PyReferenceExpression reference = (PyReferenceExpression)expression; for (final PyClass element : elements) { - if (expression.getText().equals(element.getName())) { + if (reference.getReference().isReferenceTo(element)) { expression.delete(); } } @@ -54,4 +66,11 @@ class SuperClassesManager extends MembersManager { //TODO: Check for "overrides" return new PyMemberInfo(input, false, name, false, this, false); } + + private static class NoFakeSuperClasses extends NotNullPredicate { + @Override + protected boolean applyNotNull(@NotNull final PyClass input) { + return !PyNames.FAKE_OLD_BASE.equals(input.getName()); + } + } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/TypeSafeMovingStrategy.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/TypeSafeMovingStrategy.java index a55a7d215346..2cf9efda6d4a 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/TypeSafeMovingStrategy.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/TypeSafeMovingStrategy.java @@ -66,7 +66,5 @@ class TypeSafeMovingStrategy { for (final PyElement element : newElements) { PyClassRefactoringUtil.restoreNamedReferences(element); } - - PyClassRefactoringUtil.optimizeImports(myFrom.getContainingFile()); // To remove unneeded imports from source } } diff --git a/python/testData/refactoring/extractsuperclass/importNotBroken.after.py b/python/testData/refactoring/extractsuperclass/importNotBroken.after.py new file mode 100644 index 000000000000..897c62a71cfc --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/importNotBroken.after.py @@ -0,0 +1,9 @@ +from shared import SharedClass + + +class DestClass(SharedClass): + pass + + +class Source(DestClass): + pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/importNotBroken.before.py b/python/testData/refactoring/extractsuperclass/importNotBroken.before.py new file mode 100644 index 000000000000..82d6d5c04c20 --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/importNotBroken.before.py @@ -0,0 +1,5 @@ +from shared import SharedClass + + +class Source(SharedClass): + pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/dest_module.after.py b/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/dest_module.after.py new file mode 100644 index 000000000000..6189ad4fc76b --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/dest_module.after.py @@ -0,0 +1,5 @@ +from shared_module import SharedClass + + +class NewParent(SharedClass): + pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/dest_module.py b/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/dest_module.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/shared_module.py b/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/shared_module.py new file mode 100644 index 000000000000..da81c49673f4 --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/shared_module.py @@ -0,0 +1 @@ +class SharedClass(object): pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/source_module.after.py b/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/source_module.after.py new file mode 100644 index 000000000000..b9d711783970 --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/source_module.after.py @@ -0,0 +1,5 @@ +from dest_module import NewParent + + +class MyClass(NewParent): + pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/source_module.py b/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/source_module.py new file mode 100644 index 000000000000..1824aec09d61 --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/importNotBrokenManyFiles/source_module.py @@ -0,0 +1,4 @@ +from shared_module import SharedClass + +class MyClass(SharedClass): + pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/instanceNotDeclaredInInit.after.py b/python/testData/refactoring/extractsuperclass/instanceNotDeclaredInInit.after.py new file mode 100644 index 000000000000..ef3511dc8c66 --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/instanceNotDeclaredInInit.after.py @@ -0,0 +1,8 @@ +class Parent(object): + def __init__(self): + self.eggs = 12 + + +class Child(Parent): + def foo(self): + self.eggs = 12 diff --git a/python/testData/refactoring/extractsuperclass/instanceNotDeclaredInInit.before.py b/python/testData/refactoring/extractsuperclass/instanceNotDeclaredInInit.before.py new file mode 100644 index 000000000000..9c5594a80067 --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/instanceNotDeclaredInInit.before.py @@ -0,0 +1,3 @@ +class Child(object): + def foo(self): + self.eggs = 12 diff --git a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/dest_module.after.py b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/dest_module.after.py index 9b1670daef11..bbf726c919ae 100644 --- a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/dest_module.after.py +++ b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/dest_module.after.py @@ -7,4 +7,7 @@ class NewParent(object): @abstractmethod def foo_method(self): + """ + Foo + """ pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/source_module.after.py b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/source_module.after.py index 2da9618b43f0..6e71752a511d 100644 --- a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/source_module.after.py +++ b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/source_module.after.py @@ -3,4 +3,7 @@ from dest_module import NewParent class MyClass(NewParent): def foo_method(self): + """ + Foo + """ spam = "eggs" \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/source_module.py b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/source_module.py index ed2d3668c71e..f4a7c42a7df4 100644 --- a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/source_module.py +++ b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstract/source_module.py @@ -1,3 +1,6 @@ class MyClass(object): def foo_method(self): + """ + Foo + """ spam = "eggs" \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/dest_module.after.py b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/dest_module.after.py index deeb3e9b11d9..ddce4a86100f 100644 --- a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/dest_module.after.py +++ b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/dest_module.after.py @@ -6,7 +6,7 @@ ABCMeta() abstractmethod() -class NewParent(object, metaclass=ABCMeta): +class NewParent(metaclass=ABCMeta): @classmethod @abstractmethod def foo_method(cls): diff --git a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/source_module.py b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/source_module.py index 4bcb1e7aca56..b197c5fc2bfa 100644 --- a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/source_module.py +++ b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/source_module.py @@ -1,5 +1,4 @@ -from shared_module import object -class MyClass(object): +class MyClass(): @classmethod def foo_method(cls): spam = "eggs" \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/moveExtends/source_module.after.py b/python/testData/refactoring/extractsuperclass/moveExtends/source_module.after.py index b5e6cda02c55..b9d711783970 100644 --- a/python/testData/refactoring/extractsuperclass/moveExtends/source_module.after.py +++ b/python/testData/refactoring/extractsuperclass/moveExtends/source_module.after.py @@ -1,5 +1,5 @@ from dest_module import NewParent -import shared_module -class MyClass(shared_module.TheParentOfItAll, NewParent): + +class MyClass(NewParent): pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/dest_module.after.py b/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/dest_module.after.py new file mode 100644 index 000000000000..1171abf73b10 --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/dest_module.after.py @@ -0,0 +1,5 @@ +from shared_module import TheParentOfItAll + + +class NewParent(TheParentOfItAll): + pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/dest_module.py b/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/dest_module.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/shared_module.py b/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/shared_module.py new file mode 100644 index 000000000000..fd3d3fc7bf88 --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/shared_module.py @@ -0,0 +1,2 @@ +class TheParentOfItAll(object): + pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/source_module.after.py b/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/source_module.after.py new file mode 100644 index 000000000000..b9d711783970 --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/source_module.after.py @@ -0,0 +1,5 @@ +from dest_module import NewParent + + +class MyClass(NewParent): + pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/source_module.py b/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/source_module.py new file mode 100644 index 000000000000..c2ca97497a33 --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/moveExtendsCheckReference/source_module.py @@ -0,0 +1,4 @@ +from shared_module import TheParentOfItAll + +class MyClass(TheParentOfItAll): + pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/presenter/file.py b/python/testData/refactoring/extractsuperclass/presenter/file.py index e557238f69b6..9e6af14bf862 100644 --- a/python/testData/refactoring/extractsuperclass/presenter/file.py +++ b/python/testData/refactoring/extractsuperclass/presenter/file.py @@ -12,4 +12,9 @@ class Child(object, date): class StaticOnly(object): @staticmethod def static_method(): # May be abstract in case of Py3 + pass + + +class OldClass(): + def foo(self): pass \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/shared.py b/python/testData/refactoring/extractsuperclass/shared.py new file mode 100644 index 000000000000..71f03ab781fc --- /dev/null +++ b/python/testData/refactoring/extractsuperclass/shared.py @@ -0,0 +1 @@ +class SharedClass: pass \ No newline at end of file diff --git a/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/Class.after.py b/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/Class.after.py index 91ab0b1c2b02..c0e75fa98646 100644 --- a/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/Class.after.py +++ b/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/Class.after.py @@ -1,6 +1,10 @@ from SuperClass import Parent class Child(Parent): def my_method(self, foo): + """ + Eats eggs + :param foo: eggs + """ bar = foo @classmethod diff --git a/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/Class.py b/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/Class.py index 91ab0b1c2b02..c0e75fa98646 100644 --- a/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/Class.py +++ b/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/Class.py @@ -1,6 +1,10 @@ from SuperClass import Parent class Child(Parent): def my_method(self, foo): + """ + Eats eggs + :param foo: eggs + """ bar = foo @classmethod diff --git a/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/SuperClass.after.py b/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/SuperClass.after.py index 8311ea6a630a..ded9ea669c57 100644 --- a/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/SuperClass.after.py +++ b/python/testData/refactoring/pullup/abstractMethodPy3AddMeta/SuperClass.after.py @@ -4,6 +4,10 @@ from abc import object class Parent(object, metaclass=ABCMeta): @abstractmethod def my_method(self, foo): + """ + Eats eggs + :param foo: eggs + """ pass @classmethod diff --git a/python/testData/refactoring/pullup/instanceNotDeclaredInInit.after.py b/python/testData/refactoring/pullup/instanceNotDeclaredInInit.after.py new file mode 100644 index 000000000000..812bd9ce6364 --- /dev/null +++ b/python/testData/refactoring/pullup/instanceNotDeclaredInInit.after.py @@ -0,0 +1,8 @@ +class Parent(object): + def __init__(self): + self.foo = 12 + + +class Child(Parent): + def foo(self): + self.foo = 12 diff --git a/python/testData/refactoring/pullup/instanceNotDeclaredInInit.py b/python/testData/refactoring/pullup/instanceNotDeclaredInInit.py new file mode 100644 index 000000000000..9e57575195c5 --- /dev/null +++ b/python/testData/refactoring/pullup/instanceNotDeclaredInInit.py @@ -0,0 +1,7 @@ +class Parent(object): + pass + + +class Child(Parent): + def foo(self): + self.foo = 12 diff --git a/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterTest.java b/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterTest.java index 4168bdcdfc48..accfbab8bd3f 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterTest.java @@ -116,6 +116,15 @@ public class PyExtractSuperclassPresenterTest return errorMessageCapture; } + /** + * Old classes could be refactored as well + */ + public void testOldClass() { + final Collection members = launchAndGetMembers("OldClass"); + final Matcher> matcher = Matchers + .containsInAnyOrder(new PyPresenterTestMemberEntry("foo(self)", true, false, true)); + compareMembers(members, matcher); + } /** * Checks that class fields could be moved while "extends object" is not in list diff --git a/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassTest.java b/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassTest.java index d05ed2e3cace..13d5b4d4f253 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassTest.java @@ -54,6 +54,11 @@ public class PyExtractSuperclassTest extends PyClassRefactoringTest { multiFileTestHelper("TheParentOfItAll", false); } + // Checks that moving methods between files moves superclass expressions regardless import style (q.name or name) + public void testMoveExtendsCheckReference() throws Throwable { + multiFileTestHelper("TheParentOfItAll", false); + } + // Extracts method as abstract public void testMoveAndMakeAbstract() throws Throwable { multiFileTestHelper(".foo_method", true); @@ -93,6 +98,10 @@ public class PyExtractSuperclassTest extends PyClassRefactoringTest { doSimpleTest("Foo", "Suppa", null, true, ".foo"); } + public void testInstanceNotDeclaredInInit() throws Exception { + doSimpleTest("Child", "Parent", null, true, "#eggs"); + } + public void testWithSuper() throws Exception { doSimpleTest("Foo", "Suppa", null, true, ".foo"); } @@ -101,6 +110,17 @@ public class PyExtractSuperclassTest extends PyClassRefactoringTest { doSimpleTest("A", "Suppa", null, false, ".foo"); } + // PY-12175 + public void testImportNotBroken() throws Exception { + myFixture.copyFileToProject("/refactoring/extractsuperclass/shared.py", "shared.py"); + doSimpleTest("Source", "DestClass", null, true, "SharedClass"); + } + + // PY-12175 but between several files + public void testImportNotBrokenManyFiles() throws Exception { + multiFileTestHelper("SharedClass", false); + } + public void testMoveFields() throws Exception { doSimpleTest("FromClass", "ToClass", null, true, "#instance_field", "#CLASS_FIELD"); } diff --git a/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpTest.java b/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpTest.java index 6b3a39c54c75..d18f5b79e3c2 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpTest.java @@ -64,6 +64,10 @@ public class PyPullUpTest extends PyClassRefactoringTest { doHelperTest("Child", "#CLASS_VAR", "Parent"); } + public void testInstanceNotDeclaredInInit() { + doHelperTest("Child", "#foo", "Parent"); + } + public void testMoveClassAttributesNoPass() { doHelperTest("Child2", "#CLASS_VAR", "Parent2"); } diff --git a/resources-en/src/tips/UmlClassDiagram.html b/resources-en/src/tips/UmlClassDiagram.html deleted file mode 100644 index fba09a6258d8..000000000000 Binary files a/resources-en/src/tips/UmlClassDiagram.html and /dev/null differ diff --git a/resources-en/src/tips/images/umlClassDiagram.png b/resources-en/src/tips/images/umlClassDiagram.png deleted file mode 100644 index b5d759e0c2a8..000000000000 Binary files a/resources-en/src/tips/images/umlClassDiagram.png and /dev/null differ diff --git a/resources-en/src/tips/tips.xml b/resources-en/src/tips/tips.xml deleted file mode 100644 index 503f38594771..000000000000 --- a/resources-en/src/tips/tips.xml +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/src/META-INF/IdeTipsAndTricks.xml b/resources/src/META-INF/IdeTipsAndTricks.xml new file mode 100644 index 000000000000..fa9847f3dc99 --- /dev/null +++ b/resources/src/META-INF/IdeTipsAndTricks.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 88c827c78da6..e6d8f987a166 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -2,6 +2,7 @@ com.intellij IDEA CORE + diff --git a/xml/dom-tests/tests/com/intellij/util/xml/DomIncludesTest.java b/xml/dom-tests/tests/com/intellij/util/xml/DomIncludesTest.java index 732c36b20325..095c9d40b050 100644 --- a/xml/dom-tests/tests/com/intellij/util/xml/DomIncludesTest.java +++ b/xml/dom-tests/tests/com/intellij/util/xml/DomIncludesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -16,7 +16,6 @@ package com.intellij.util.xml; import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction; -import com.intellij.concurrency.JobLauncher; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.vfs.VfsUtil; @@ -122,23 +121,23 @@ public class DomIncludesTest extends CodeInsightFixtureTestCase { final AtomicReference ex = new AtomicReference(); for (int j = 0; j < threadCount; j++) { - JobLauncher.getInstance().submitToJobThread(0, new Runnable() { + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override public void run() { try { - for (int k = 0; k < iterationCount; k++) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { + for (int k = 0; k < iterationCount; k++) { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { final List boys = rootElement.getBoys(); Thread.yield(); final List children = rootElement.getChildren(); Thread.yield(); assertEquals(boys, rootElement.getBoys()); assertEquals(children, rootElement.getChildren()); - } - }); - Thread.yield(); - } + } + }); + Thread.yield(); + } } catch (Exception e) { ex.set(e);